In juejin. Cn/post / 699877… In this section, we will analyze blocks from the source point of view and see how it is implemented inside.

Clang see block

#include "stdio.h"

int main(){

    void(^block)(void) = ^{
        printf("This is a block.");
    };
    
     block();
    return 0;
}
Copy the code

Use Clang to view the source code

  void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                           (void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA
                                                           ));
Copy the code

Let’s look at __main_block_IMPL_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The first argument passed in block is __main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("This is a block.");
    }
Copy the code

This is a function, and the first argument is the function address, passing a call to the function block

 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
Copy the code

The structure of __block_impl is as follows

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
Copy the code

Remove the cast block->FuncPtr(block) which calls the __main_block_func_0 function, passing it as block

Block capture variable

#include "stdio.h"
int main(){
    int a =0;
    void(^block)(void) = ^{
        printf("This is a block%d", a);
    };
    
     block();
    return 0;
}
Copy the code

After the clang

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; / / 1
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {/ / 2impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy //3

        printf("This is a block%d", a);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
    int a =0;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0.2 };
Copy the code

FuncPtr(block) int a = __cself->a; FuncPtr(block) int a = __cself->a Isa = &_nsconcretestackblock; 📢 we know this isa heap block, but inpl. isa = &_nsconcretestackblock; It says it’s a stack block, we haven’t run it yet, so when does it become a stack block, what happens in between

We know that we need to modify a with __block in order to modify a in the block

Is decorated __block

int main(){
    __block  int a  = 0;
    void(^block)(void) = ^{
        a = 5;
        printf("This is a block%d", a);
    };
    
     block();
    return 0;
}
Copy the code

After the clang


struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = 5;
        printf("This is a block%d", (a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(){
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
    
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA,
                                                           (__Block_byref_a_0 *)&a,
                                                           570425344));

     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0.2 };
Copy the code
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA,
                                                           (__Block_byref_a_0 *)&a,
                                                           570425344));
Copy the code

We can see that the third argument to block is (__Block_byref_a_0 *)&a. After being modified by __block, a becomes __Block_byref_a_0 as shown in __Block_byref_a_0

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = 
 {
        (void*)0,
        (__Block_byref_a_0 *)&a,
        0,
        sizeof(__Block_byref_a_0),
        0
  };
Copy the code

A points to __forvarding from the function call


 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA,
                                                           (__Block_byref_a_0 *)&a,
                                                           570425344));

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = 5;
        printf("This is a block%d", (a->__forwarding->a));
    }
Copy the code

__Block_byref_a_0 *)&a a address, so __Block_byref_a_0 *a = __cself->a; A on the inside and a on the outside refer to the same A. It can be modified.

_Block_copy

Let’s make a break point, let’s look at assembly,We saw that call in thereobjc_retainBlock

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}
Copy the code

Let’s look at _Block_copy


void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if(! arg)return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block. Make a copy. -> heap
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if(! result)return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refCount -- Object ISA consortium is located
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        returnresult; }}Copy the code

We see that if the block is on the stack, copy it to the heap, and result->isa = _NSConcreteMallocBlock;

The structure of the Block

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

// KC annotation :Block structure
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
Copy the code

Block is a structure at the bottom, we see that Block_descriptor_3 has signature in it and in some block hooks, we’ll take the block signature, store it, and at the appropriate time for message forwarding, trigger the signature call to implement the block hook. What is the relationship between BLOCK_DESCRIPTOR_2, BLOCK_DESCRIPTOR_3, and BLOCK_DESCRIPTOR_1

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

// KC comment: Description of Block: signature related
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}
Copy the code

The BLOCK_HAS_COPY_DISPOSE flag is used to determine whether block_has_descriptor_2 is present, then desc += sizeof(struct Block_descriptor_1); Offset the size of Block_descriptor_1. Similarly Block_descriptor_3. You can see that their addresses are contiguous. Look at this mark


enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if ! BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
Copy the code