A Block is an anonymous function with automatic variable values.

Block is frequently used in iOS daily development and is very convenient to use. However, many friends know nothing about its implementation principle and mechanism. Block is an anonymous function with automatic variable values, which can automatically capture the parameters used in the function. This paper will analyze the implementation principle of Block in detail.

First, the implementation of Block

In exploring the Block implementation principle, the command line tool Clang is very useful, it can be converted into C++ source code, convenient for us to understand the implementation principle.

clang -rewrite-objc main.m
Copy the code

We can use the above command to try to convert the following code into C++ source code to analyze the concrete implementation of the Block:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int tempVar = 1;
        void (^blk)(void) = ^() {
            printf("Block var:%d\n", tempVar);
        };
        blk();    
    }
    return 0;
}
Copy the code

After conversion and elimination of redundant code:

struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int tempVar; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tempVar, int flags=0) : tempVar(_tempVar) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int tempVar = __cself->tempVar; // bound by copyprintf("Block var:%d\n", tempVar); } 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 argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int tempVar = 1; void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); }return 0;
}
Copy the code

After the transformation, we have a long string of code, and we will analyze the actual meaning of this code.

The first part is __block_impl, which is the lowest structure of a Block implementation:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
Copy the code
  • Isa: indicates that it has the same properties as the object.
  • Flag: indicates the status Flag bit.
  • Reserved: indicates the Reserved memory size for the upgrade.
  • FuncPtr: function pointer.

__main_block_desc_0 is a structure that manages Block memory usage:

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)};
Copy the code
  • Reserved: indicates the Reserved memory size for the upgrade.
  • Block_size: Block size.

The third part is the Block implementation structure:

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

The structure’s naming logic is __ function name _block_IMPL_ order within the function.

  • Impl:__block_implType struct, refer to Part 1.
  • Desc:__main_block_impl_0Structure instance size.
  • TempVar: Captured automatic variable value.
  • __main_block_impl_0:__main_block_impl_0The constructor of a structure.

__main_block_func_0:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int tempVar = __cself->tempVar; // bound by copy
  printf("Block var:%d\n", tempVar);
}
Copy the code

The __main_BLOCK_IMPL_0 structure captures the value of the automatic variable as a member variable, which is then copied and used when called.

The fifth part for the main function after conversion source:

int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int tempVar = 1; void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); }return 0;
}
Copy the code

In addition to a local variable, tempVar, the other two lines of code are the initialization part of the Block and the call part. After removing part of type strong code, it is as follows:

void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
(blk->FuncPtr)(blk);
Copy the code

The first line of code passes the previously declared __main_block_func_0 and &__main_block_DESC_0_data to the constructor to get an instance of the BLK structure.

The second line of code is the function pointer that calls BLK.

Capture variable values

2.1 Automatic Variables

When an external local variable is used in a Block, it is automatically captured and becomes a member variable of the Block structure so that it can be accessed within the Block. In addition, there are several other ways to access external variables. Here are the types of variables and their corresponding scopes:

  1. Automatic variables: captured in blocks.
  2. Static variables: available in scope.
  3. Global variables: The entire program is available.
  4. Static global variables: Current file available.

Analyze the access to various types of variables in a Block by converting the following code to C++ code:

static char globalVar[] = {"globalVar"};
static char globalStaticVar[] = {"globalStaticVar"};

void catchVar() {
    int var1 = 1;
    int var2 = 2;
    static char staticVar[] = {"staticVar"};
    
    void (^blk)(void) = ^{
        printf("%d\n", var1);
        printf("%s\n", staticVar);
        printf("%s\n", globalVar);
        printf("%s\n", globalStaticVar);
    };
    blk();
}
Copy the code

The code above uses local variables, static variables, global variables, and static global variables respectively.

struct __catchVar_block_impl_0 { struct __block_impl impl; struct __catchVar_block_desc_0* Desc; int var1; Char (*staticVar)[10]; __catchVar_block_impl_0(void *fp, struct __catchVar_block_desc_0 *desc, int _var1, char (*_staticVar)[10], int flags=0) : var1(_var1), staticVar(_staticVar) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __catchVar_block_func_0(struct __catchVar_block_impl_0 *__cself) { int var1 = __cself->var1; // bound by copy char (*staticVar)[10] = __cself->staticVar; // bound by copyprintf("%d\n", var1);
        printf("%s\n", (*staticVar));
        printf("%s\n", globalVar);
        printf("%s\n", globalStaticVar);
    }

static struct __catchVar_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __catchVar_block_desc_0_DATA = { 0, sizeof(struct __catchVar_block_impl_0)};
void catchVar() {
    int var1 = 1;
    int var2 = 2;
    static char staticVar[] = {"staticVar"};

    void (*blk)(void) = ((void (*)())&__catchVar_block_impl_0((void *)__catchVar_block_func_0, &__catchVar_block_desc_0_DATA, var1, &staticVar));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
Copy the code

The __catchVar_block_func_0 function and the Block constructor let you know how various variables are accessed:

  • Both global and static global variables are directly accessible within their scope.
  • A static variable becomes a member variable, but what is passed in from the constructor is a memory address, which is then accessed via the address.
  • A local variable becomes a member variable, its value is passed in directly from the constructor and assigned to the member variable, which is then accessed through the member variable.

2.2 object

In the following code, an external object is used inside the Block. How does the object be captured inside the Block?

void catchObject() {
    id obj = [NSObject new];
    
    void (^blk)(void) = ^{
        printf("%d\n", [obj hash]);
    };
    blk();
}
Copy the code

After converting the above code into C++ code, we analyze the implementation principle:

struct __catchObject_block_impl_0 { struct __block_impl impl; struct __catchObject_block_desc_0* Desc; __strong id obj; __catchObject_block_impl_0(void *fp, struct __catchObject_block_desc_0 *desc, __strong id _obj, int flags=0) : obj(_obj) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __catchObject_block_func_0(struct __catchObject_block_impl_0 *__cself) { __strong id obj = __cself->obj; // bound by copyprintf("%d\n", ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("hash"))); } static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); } static struct __catchObject_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __catchObject_block_impl_0*, struct __catchObject_block_impl_0*); void (*dispose)(struct __catchObject_block_impl_0*); } __catchObject_block_desc_0_DATA = { 0, sizeof(struct __catchObject_block_impl_0), __catchObject_block_copy_0, __catchObject_block_dispose_0}; voidcatchObject() {
    id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));

    void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
Copy the code

The catchObject() function builds the BLK with the object obj and decimal flag bit 570425344.

void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
Copy the code

The member variable obj in the Block structure is the __strong modifier. The object obj is directly assigned to the member variable, indicating that the original object is used directly and the reference count is +1.

__catchObject_block_copy_0 and __catchObject_block_dispose_0 have been added to the source code. These two methods, in turn, call the _Block_object_assign and _Block_object_dispose methods, which manage the variable store in the Block, as discussed below.

static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {
  _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {
  _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
Copy the code

2.3 Block

Use one Block as a parameter in another Block, and then analyze the implementation of the Block in this case.

void catchBlock() {
    void (^block)(void) = ^{};
    void (^blk)(void) = ^{
        block;
    };
    blk();
}
Copy the code

The code after conversion is as follows:

struct __catchBlock_block_impl_0 {
  struct __block_impl impl;
  struct __catchBlock_block_desc_0* Desc;
  __catchBlock_block_impl_0(void *fp, struct __catchBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchBlock_block_func_0(struct __catchBlock_block_impl_0 *__cself) {
}

static struct __catchBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __catchBlock_block_desc_0_DATA = { 0, sizeof(struct __catchBlock_block_impl_0)};

struct __catchBlock_block_impl_1 {
  struct __block_impl impl;
  struct __catchBlock_block_desc_1* Desc;
  struct __block_impl *block;
  __catchBlock_block_impl_1(void *fp, struct __catchBlock_block_desc_1 *desc, void *_block, int flags=0) : block((struct __block_impl *)_block) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __catchBlock_block_func_1(struct __catchBlock_block_impl_1 *__cself) {
  void (*block)() = (void (*)())__cself->block; // bound by copy

        block;
    }
static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {_Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static struct __catchBlock_block_desc_1 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __catchBlock_block_impl_1*, struct __catchBlock_block_impl_1*);
  void (*dispose)(struct __catchBlock_block_impl_1*);
} __catchBlock_block_desc_1_DATA = { 0, sizeof(struct __catchBlock_block_impl_1), __catchBlock_block_copy_1, __catchBlock_block_dispose_1};

void catchBlock() {
    void (*block)(void) = ((void (*)())&__catchBlock_block_impl_0((void *)__catchBlock_block_func_0, &__catchBlock_block_desc_0_DATA));
    void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
Copy the code

The catchBlock() constructor passes in the Block and flag bit 570425344, which is assigned to a Block member variable of type __block_impl in the __catchBlock_block_impl_1 structure.

void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
Copy the code

There are also two methods in this code: __catchObject_block_copy_0 and __catchObject_block_dispose_0. The difference is that when the _Block_object_assign and _Block_object_dispose methods are called, the final input parameter is 7 /*BLOCK_FIELD_IS_BLOCK*/, The previous object capture was passed 3 /*BLOCK_FIELD_IS_OBJECT*/.

static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {
  _Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}

static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {
  _Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
Copy the code

2.4 __block modified variables

Once a Block has captured an external variable, it can access the external variable internally, but cannot yet change the value of the external variable (static variables, global variables, and static global variables can be modified directly). In this case, the __block modifier is used so that the variable that the __block modifier modifies can be modified inside the Block.

The following is to analyze the implementation principle by converting the source code:

void catchBlockVar() {
    __block int blockVar = 1;
    
    void (^blk)(void) = ^{
        blockVar = 2;
        printf("%d\n", blockVar);
    };
    blk();
}
Copy the code

After the transformation:

struct __Block_byref_blockVar_0 { void *__isa; __Block_byref_blockVar_0 *__forwarding; int __flags; int __size; int blockVar; }; struct __catchBlockVar_block_impl_0 { struct __block_impl impl; struct __catchBlockVar_block_desc_0* Desc; __Block_byref_blockVar_0 *blockVar; // by ref __catchBlockVar_block_impl_0(void *fp, struct __catchBlockVar_block_desc_0 *desc, __Block_byref_blockVar_0 *_blockVar, int flags=0) : blockVar(_blockVar->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) { __Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref (blockVar->__forwarding->blockVar) = 2;printf("%d\n", (blockVar->__forwarding->blockVar)); } static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/); } static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {_Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/); } static struct __catchBlockVar_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __catchBlockVar_block_impl_0*, struct __catchBlockVar_block_impl_0*); void (*dispose)(struct __catchBlockVar_block_impl_0*); } __catchBlockVar_block_desc_0_DATA = { 0, sizeof(struct __catchBlockVar_block_impl_0), __catchBlockVar_block_copy_0, __catchBlockVar_block_dispose_0}; voidcatchBlockVar() {
    __attribute__((__blocks__(byref))) __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};

    void (*blk)(void) = ((void (*)())&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
Copy the code

The biggest difference from other blocks is the addition of a structure __Block_byref_blockVar_0, which is automatically generated for variables modified by the __block modifier.

struct __Block_byref_blockVar_0 {
  void *__isa;
__Block_byref_blockVar_0 *__forwarding;
 int __flags;
 int __size;
 int blockVar;
};
Copy the code
  • __isa: Properties that are as concrete as objects.
  • __forwarding: points to itself in the stack and is not copied, and points to the heap when copied to the heap.
  • __flags: indicates the flag bit.
  • __size: The size of memory occupied by the structure.
  • BlockVar: the value of the original variable.

After catchBlockVar is converted from int to __Block_byref_blockVar_0, the address of this structure is passed into the Block constructor. Therefore, Block automatically generates member variables of type __Block_byref_blockVar_0.

void catchBlockVar() {
    __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};

    void (*blk)(void) = (&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
Copy the code

When called by a Block, the following code shows that the blockVar is not accessed directly, but is accessed by its __forwarding value. This allows access to structures in the heap when it is copied to the heap. Why have priority access to the heap structure? Stack objects are released when they are out of scope and need to be copied to the heap if they want to be used outside of scope.

static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) {
  __Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref

        (blockVar->__forwarding->blockVar) = 2;
        printf("%d\n", (blockVar->__forwarding->blockVar));
}
Copy the code

The arguments passed in to the __catchObject_block_copy_0 and __catchObject_block_dispose_0 methods in this code are 8 /*BLOCK_FIELD_IS_BYREF*/.

static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {
  _Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {
  _Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
Copy the code

2.5 __block modified objects

Use __block-modified variables and __block-modified objects in blocks, where the internal implementation is slightly different, as analyzed in the following code.

void catchBlockObject() {
    
    __block NSObject *obj = [[NSObject alloc] init];
    blk_t block = ^ {
        obj;
    };
}
Copy the code

After the transformation:

struct __Block_byref_obj_1 { void *__isa; __Block_byref_obj_1 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSObject *obj; }; struct __catchBlockObject_block_impl_0 { struct __block_impl impl; struct __catchBlockObject_block_desc_0* Desc; __Block_byref_obj_1 *obj; // by ref __catchBlockObject_block_impl_0(void *fp, struct __catchBlockObject_block_desc_0 *desc, __Block_byref_obj_1 *_obj, int flags=0) : obj(_obj->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __catchBlockObject_block_func_0(struct __catchBlockObject_block_impl_0 *__cself) { __Block_byref_obj_1 *obj = __cself->obj; // bound by ref (obj->__forwarding->obj); } static void __catchBlockObject_block_copy_0(struct __catchBlockObject_block_impl_0*dst, struct __catchBlockObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/); } static void __catchBlockObject_block_dispose_0(struct __catchBlockObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/); } static struct __catchBlockObject_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __catchBlockObject_block_impl_0*, struct __catchBlockObject_block_impl_0*); void (*dispose)(struct __catchBlockObject_block_impl_0*); } __catchBlockObject_block_desc_0_DATA = { 0, sizeof(struct __catchBlockObject_block_impl_0), __catchBlockObject_block_copy_0, __catchBlockObject_block_dispose_0}; voidcatchBlockObject() {

    __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

    blk_t block = ((void (*)())&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
Copy the code

In the declared structure __Block_byref_obj_1, two more methods of memory management are called __Block_byref_id_object_copy and __Block_byref_id_object_dispose.

struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};
Copy the code

In the catchBlockObject() function, an obj object modified by the __block modifier is converted to a __Block_byref_obj_1 structure. Copy and dispose methods pass in __Block_byref_id_object_copy_131 and __Block_byref_id_object_dispose_131 static methods.

void catchBlockObject() {

   __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

    blk_t block = (&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
Copy the code

The static method is as follows, passing in 131 as the last parameter, which is 3 + 128.

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
Copy the code

The enumeration below shows why different input parameters are required to capture different types of variables. The operations for copying and releasing captured variables are different depending on the input parameter. 131 said BLOCK_FIELD_IS_BYREF | BLOCK_BYREF_CALLER.

// Runtime support functions used by compiler when generating copy/dispose helpers

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
Copy the code

The Copy/Dispose method of the Block itself takes 8 /*BLOCK_FIELD_IS_BYREF*/.

3. Storage domain of Block

The storage domains of blocks are divided into three types: _NSConcreteStackBlock, _NSConcreteGlobalBlock, and _NSConcreteMallocBlock.

  • _NSConcreteStackBlock: stack area
  • _NSConcreteGlobalBlock: Data section (.data section)
  • _NSConcreteMallocBlock: heap area

3.1 _NSConcreteStackBlock

Normally, blocks defined inside a class are on the stack if automatic variables are captured, and their types can be printed out in the following code. In practice, however, both definitions are assigned to a variable, which will cause the Block to become _NSConcreteMallocBlock when used in practice.

Int argc, const char * argv[]) {@autoreleasepool {int tempVar = 1; NSLog(@"Stack Block:%@\n"And ^ () {printf("Stack Block! %d\n", tempVar);
        });
    }
    return0; } / /printf: Stack Block: < __NSStackBlock__ : 0 x7ffeefbff4a0 >Copy the code

3.2 _NSConcreteGlobalBlock

The Block type defined in an area where global variables are defined is _NSConcreteGlobalBlock, or the Block defined inside a class is also of _NSConcreteGlobalBlock if no automatic variables are captured.

Int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@)"Global Block:%@\n"And ^ () {printf("Global Block! \n");
        });
    }
    return0; } / /printf: Global Block: < __NSGlobalBlock__ : 0 x1000021c8 >Copy the code

3.3 _NSConcreteMallocBlock

This is because a Block captured __block variable that exists on the stack is released when it is out of scope. To avoid this, the Block implementation mechanism copies blocks from the stack to the heap, so that the Block and __block variables of the heap remain even when out of scope.

When not copied, the __block variable __forwarding points to itself, and when copied points to a __block variable in the heap. This mechanism makes it accessible in both heap and stack.

These are the scenarios where the compiler automatically copies blocks to the heap:

  • Blocks are automatically copied to the heap as the return value of a function or method when ARC is in effect
  • The method names in the Cocoa framework include usingBlock and so on
  • The GCD API
  • When a block is assigned to an object of type __Strong modifier ID or to a member variable of type block

The following scenarios need to be manually copied to the heap

  • Call the block instance method manually
    • Using a block as a parameter in a method requires manual copying by the developer
    • You need to manually copy a block when putting it in an array as a return value

Storage fields for __block variables

When a Block is copied from the stack to the heap, the variables modified by the corresponding __block modifier are also copied to the heap.

The __block variable is converted to a structure containing the member variable __forwarding, which can be used when the Block variable is out of scope after being copied to the heap. At this point, the stack structure member variable __forwarding points to the heap structure (or to itself if it is not copied).

When multiple blocks use the same __block variable, the reference count of the copied __block variable on the heap is increased, and the reference count is reduced to zero before the __block variable is discarded when released.

5. _Block_object_assign and _Block_object_dispose

In Section 2, we saw how Block calls to the _Block_object_assign function use different flags when capturing different types of variables.

5.1 _Block_object_assign

Directly look at the implementation source code:

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      caseBLOCK_FIELD_IS_OBJECT: /******* id object = ... ; [^{ object; } copy]; ********/ _Block_retain_object(object); *dest = object;break;

      caseBLOCK_FIELD_IS_BLOCK: /******* void (^object)(void) = ... ; [^{ object; } copy]; ********/ *dest = _Block_copy(object);break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held inthe __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper  directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/ *dest = object;break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break; }}Copy the code

The _Block_object_assign method does different processing based on the flags input parameter.

5.1.1 BLOCK_FIELD_IS_OBJECT: Replication object

// Default _Block_retain_object is assigned to _Block_retain_object_default, which does nothing. // The pointer points to the original object memory address. *dest = object;Copy the code

The _Block_retain_object method is only useful when _Block_use_RR2 is executed.

void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
    _Block_retain_object = callbacks->retain;
    _Block_release_object = callbacks->release;
    _Block_destructInstance = callbacks->destructInstance;
}
Copy the code

5.1.2 BLOCK_FIELD_IS_BLOCK: Replication Block

Implementation of _Block_copy

// Copy or collision reference counts. If you do want to copy, call the replication helper (if present). void *_Block_copy(const void *arg) { struct Block_layout *aBlock;if(! arg)returnNULL; aBlock = (struct Block_layout *)arg; // Increases the reference count if it is copiedif (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        returnaBlock; } // If it is a global Block, return it directlyelse if (aBlock->flags & BLOCK_IS_GLOBAL) {
        returnaBlock; } // Make a copyelse {
        // Its a stack block.  Make a copy.
        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
        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);
        result->isa = _NSConcreteMallocBlock;
        returnresult; }}Copy the code

5.1.3 BLOCK_FIELD_IS_BYREF: Copy the _block variable

_Block_byref_copy

static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if((SRC ->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {// copy Block_byref struct Block_byref *copy = (struct Block_byref)  *)malloc(src->size); copy->isa = NULL; // byref value 4 is logical refcount of 2: onefor caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;

        ifSRC ->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {// copy Block_byref2, Struct Block_byref_2 *src2 = (struct Block_byref_2 *)(SRC +1); struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1); copy2->byref_keep = src2->byref_keep; copy2->byref_destroy = src2->byref_destroy;if(SRC ->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {// Copy Block_byref3 struct Block_byref_3 *src3 = (struct Block_byref_3) *)(src2+1); struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1); copy3->layout = src3->layout; } (*src2->byref_keep)(copy, src); }else {
            // Bitwise copy.
            // This copy includes Block_byref_3, ifany. memmove(copy+1, src+1, src->size - sizeof(*src)); }} // The reference count that has been copied to the heap is +1else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
Copy the code

5.1.4 ensuring other

In other cases it is a direct pointer to the original object address:

*dest = object;
Copy the code

5.2 _Block_object_dispose

Here is the release logic for a Block to capture a variable:

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break; }}Copy the code

5.2.1 BLOCK_FIELD_IS_BYREF: Release the _block variable

Implementation of _Block_byref_release

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?) byref = byref->forwarding; If (byref->flags & BLOCK_BYREF_NEEDS_FREE) {int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK; os_assert(refcount); / / reference count - 1 after the judgment of whether to release the operation if (latching_decr_int_should_deallocate (& byref - > flags)) {/ / whether the variable is copy/if the dispose method (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1); (*byref2->byref_destroy)(byref); } free(byref); }}}Copy the code

The _block variable member variables and flag bits are used to determine the release step.

5.2.2 BLOCK_FIELD_IS_BLOCK: Releases blocks

Implementation of _Block_release

void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if(! aBlock)return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return; // Global blocks and blocks on the stack directly return // Block reference count -1 to determine whether to freeif(latching_decr_int_should_deallocate(&aBlock->flags)) { _Block_call_dispose_helper(aBlock); _Block_destructInstance(aBlock); free(aBlock); }}Copy the code

5.2.3 BLOCK_FIELD_IS_OBJECT: Release an object

_Block_release_object(object);
Copy the code

The _Block_release_object method does not operate in an ARC environment, as does _Block_retain_object.

Circular reference

A circular reference is raised when a Block is a member variable of a class and an instance of the class is used inside the Block. In this case, the class instance holds the member variable block, the block holds the member variable __block, and the __block variable structure holds the class instance, forming a triangular circular reference relationship.

__block id tmp = self;
blk = ^{
  NSLog(@"self = %@", tmp);
}
Copy the code

There are two ways to solve circular references. One is to use the __weak modifier, which breaks the relationship between the __block variable structure and the class instance, thus avoiding circular references.

__weak id tmp = self;
blk = ^{
  NSLog(@"self = %@", tmp);
}
Copy the code

Another option is to use the __block modifier, and then BLK calls the function to manually empty TMP on the last line. This also avoids circular references, but if BLK is not called, it will cause circular references as well. So it’s safer to use the __weak modifier.

__block id tmp = self;
blk = ^{
  NSLog(@"self = %@", tmp);
  tmp = nil;
}
Copy the code

conclusion

Write down the whole article, the most important concepts:

  • Blocks that capture variables of different types generate different logic.
  • The scoping mechanism of blocks is so that blocks and variables used internally can be used beyond their scope.
  • Copy and release logic is also different for different types of variables or __block variable structures.

reference

  • Objective-c advanced programming

Check out more of my articles here