preface

As an iOS developer, I’m not unfamiliar with blocks. They are almost the type we developers use the most, but we often stay in the level of how to use them, and we don’t know much about the underlying implementation principle of blocks. Today we will analyze the underlying principle of block.

The preparatory work

  • Objc – 818.2 –
  • libclosure-79

blockBasic grammar

Block statement:

returnType (^ blockName)(params)
Copy the code

Block assignment:

block = ^returnType(params){
     
};
Copy the code
  • Since the compiler can determine the return value type of a block from its variables, the return value type can generally be omitted

A block that returns an int and takes an int can be declared as follows:

int (^xqBlock)(int) = ^(int a){
    return a++;
};
Copy the code

The call is also simple:

xqBlock(10);
Copy the code

blockThe type of

When the block does not capture any external variables, or only uses global variables, its type is __NSGlobalBlock__(global block).

int a = 8; int main(int argc, const char * argv[]) { @autoreleasepool { void (^xqBlock)(void) = ^{ NSLog(@"%d",a); }; xqBlock(); NSLog(@"%@",xqBlock); } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-23 15:08:37. 618811 + 0800 KCBlockBuild[27580:431773] 8 2022-02-23 15:08:37.619485+0800 KCBlockBuild[27580:431773] <__NSGlobalBlock__: 0x100004030> Program ended with exit code: 0Copy the code
  • Only global variables are used and automatic variables are not captured__NSGlobalBlock__

__NSStackBlock__(stack block) when a block captures an external variable that is not copied to the heap.

int main(int argc, const char * argv[]) { @autoreleasepool { int a = 8; void (^__weak xqBlock)(void) = ^{ NSLog(@"%d",a); }; xqBlock(); NSLog(@"%@",xqBlock); } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-23 15:10:59. 324554 + 0800 KCBlockBuild[27772:435681] 8 2022-02-23 15:10:59.325195+0800 KCBlockBuild[27772:435681] <__NSStackBlock__: 0x7ffeefbff398> Program ended with exit code: 0Copy the code
  • variableabexqBlockCapture, but due to use__weakModifier that is not automatically copied to the heap

__NSMallocBlock__(heap block) when a block captures an external variable and copies it to the heap.

int main(int argc, const char * argv[]) { @autoreleasepool { int a = 8; void (^ xqBlock)(void) = ^{ NSLog(@"%d",a); }; xqBlock(); NSLog(@"%@",xqBlock); } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-23 15:13:27. 869170 + 0800 KCBlockBuild[27977:439050] 8 2022-02-23 15:13:27.869841+0800 KCBlockBuild[27977:439050] <__NSMallocBlock__: 0x109940b90> Program ended with exit code: 0Copy the code
  • variableabexqBlockCapture, which is automatically copied to the heap

We already know that blocks are typed, so is a block an instance of a class like our ordinary objects? Next, with this query, convert the block to id and see its structure as shown below:

  • From the structure, the heapblockThe actual is__NSMallocBlock__Class, whose inheritance chain is__NSMallocBlock__ -> __NSMallocBlock -> NSBlock -> NSObject.
  • The other typesblockWith the heapblockSimilarly, I will not list them here

A change in the memory reference count of a variable captured by a block

int main(int argc, const char * argv[]) { @autoreleasepool { NSObject* objc = [[NSObject alloc]init]; NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); void (^ xqBlock)(void) = ^{ NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); }; xqBlock(); void(^__weak xqBlock1)(void) = ^{ NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); }; xqBlock1(); void (^ xqBlock2)(void) = [xqBlock1 copy]; xqBlock2(); } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-23 15:33:30. 209144 + 0800 KCBlockBuild[29465:464529] 1 2022-02-23 15:33:30.209642+0800 KCBlockBuild[29465:464529] 3 2022-02-23 15:33:30.209684+0800 KCBlockBuild[29465:464529] 4 2022-02-23 15:33:30.209708+0800 KCBlockBuild[29465:464529] 5 Program ended with exit code: 0Copy the code
  • objectobjcThe reference count after initialization is1whenobjcbexqBlockAfter capture, the reference count becomes2, because it is__NSMallocBlock__Automatically copied to the heap, so the reference count plus one becomes3
  • xqBlock1To capture theobjcThe reference count increment becomes4, because it is__NSStackBlock__, so it is not copied to the heap
  • xqBlock2isxqBlock1Copy to the heap, so the reference count plus one becomes5

From the above analysis, we can know that the variable that is captured by the block is strongly referenced by the block. If an object is strongly referenced by the block and the block also captures the object, it will cause a circular reference

@interface XQPerson : NSObject
@property(nonatomic,copy)void (^xqBlock)(void);
@end
@implementation XQPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XQPerson* person = [[XQPerson alloc]init];
        person.xqBlock = ^{
            NSLog(@"%@",person);
        };
    }
    return 0;
}
Copy the code
  • This problem is easily solved by declaring a variable before a block using __weak, which is assigned toweakPersonPoint to thepersonIn theblockInternal useweakPersonOk, to avoidpersonBeing released prematurely causes a block to executepersonIs empty, can be inblockInternal use of one__strongDeclare a local variable pairweakPersonMake a strong reference. The code is shown below:
XQPerson* person = [[XQPerson alloc]init];
__weak typeof(person) weakPerson = person;
person.xqBlock = ^{
    __strong typeof(weakPerson)strongPerson = weakPerson;
    NSLog(@"%@",strongPerson);
};
Copy the code

When using a block, however, we need to pay special attention to a problem. If we use a block to capture a variable modified by __strong inside a block, we will increase the reference count of the local variable, which will still cause a circular reference:

@interface XQPerson : NSObject
@property(nonatomic,copy)void (^xqBlock1)(void);
@end

@implementation XQPerson

@end

@interface ViewController ()
@property(nonatomic,copy)void (^xqBlock)(void);
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    XQPerson* person = [[XQPerson alloc]init];
    __weak typeof(self) weakSelf = self;
    self.xqBlock = ^{
        __strong typeof(weakSelf)strongSelf = weakSelf;
        person.xqBlock1 = ^{
            NSLog(@"%@",strongSelf);
        };
    };
    self.xqBlock();
    person.xqBlock1();
}
@end
Copy the code

Person is captured by block, strongSelf is captured by Person’s xqBlock1, which is an indirect circular reference, so we need to pay special attention to block nesting when using blocks

Source code analysis

The use of blocks and their impact on reference counting of captured objects have been analyzed. What are the underlying principles? Next, use libclosure-79 source code and clang compiled into C++ code for analysis

Start assembly debugging by defining a heap block and adding a breakpoint, as shown below:

After running, as shown below, the objc_retainBlock jumps to execution

Add a symbolic breakpoint: objc_retainBlock and continue debugging

From this, it can be determined thatblockExecuted when copied_Block_copyfunction

Open the source code for libclosure-79 and search for the _Block_copy function

In the _Block_copy function, the underlying definition of a block is a pointer to the Block_layout structure

struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // isa volatile int32_t flags; // contains ref count // int32_t reserved; // Invoke BlockInvokeFunction; Struct Block_descriptor_1 *descriptor; // imported variables }; #define BLOCK_DESCRIPTOR_1 1 struct Block_descriptor_1 { uintptr_t reserved; // Uintptr_t size; // Block size}; #define BLOCK_DESCRIPTOR_2 1 struct BLOCK_DESCRIPTOR_2 {// Optional // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; // Copy function pointer, compile time assignment BlockDisposeFunction dispose; // A function pointer to the release function, compile-time assignment}; #define BLOCK_DESCRIPTOR_3 1 struct BLOCK_DESCRIPTOR_3 {// Requires BLOCK_HAS_SIGNATURE const char *signature; // Block signature such as v8@? 0 const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT };Copy the code

flagsFlag bit resolution

// Values for Block_layout->flags to describe block objects enum { BLOCK_DEALLOCATING = (0x0001), // runtime // Bit 0 indicates whether BLOCK_REFCOUNT_MASK = (0xfffe), BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler #endif BLOCK_IS_NOESCAPE = (1 << 23), // compiler BLOCK_NEEDS_FREE = (1 << 24), // Runtime // Check whether BLOCK_HAS_COPY_DISPOSE = (1 << 25), // Compiler // Check whether copy and dispose function BLOCK_HAS_CTOR = (1 << 26), // compiler: Helpers C++ code BLOCK_IS_GC = (1 << 27), is_global = (1 << 28) Block BLOCK_USE_STRET = (1 << 29), // Compiler: undefined if! BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), // Compiler // Whether there is signature information};Copy the code

_Block_copyFunction analysis:

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) {// check whether the latches on high latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) {block return aBlock; Size_t size = Block_size(aBlock);} else {// stack -> heap copy stack block to heap // Its a stack block. Make a copy. Struct Block_layout *result = (struct Block_layout *)malloc(size); if (! result) return NULL; Memmove (result, aBlock, size); // bitcopy first #if __has_feature(ptrauth_calls) // Resign the invoke pointer as it uses address authentication. result->invoke = aBlock->invoke; #if __has_feature(ptrauth_signed_block_descriptors) if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) { uintptr_t oldDesc = ptrauth_blend_discriminator( &aBlock->descriptor, _Block_descriptor_ptrauth_discriminator); uintptr_t newDesc = ptrauth_blend_discriminator( &result->descriptor, _Block_descriptor_ptrauth_discriminator); result->descriptor = ptrauth_auth_and_resign(aBlock->descriptor, ptrauth_key_asda, oldDesc, ptrauth_key_asda, newDesc); } #endif #endif // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); Not men / / / / XXX will be the reference count is set to 1, at the same time tag need to release the result - > flags | = BLOCK_NEEDS_FREE | 2; // logical refcount 1 // get copy and execute _Block_call_copy_helper(result, aBlock); // Set ISA last so memory analysis tools see a fully-initialized object _NSConcreteMallocBlock; return result; }}Copy the code
  • if (aBlock->flags & BLOCK_NEEDS_FREE)Determine if you need to release
  • if (aBlock->flags & BLOCK_IS_GLOBAL)Checks whether it is a global block and returns it if it isaBlock
  • size_t size = Block_size(aBlock);To obtainblockThe size of the
  • struct Block_layout *result = (struct Block_layout *)malloc(size);Open up in the heap areasizeSize of memory space and assign to result
  • memmove(result, aBlock, size);willaBlockCopy toresultMemory space
  • result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);Reset reference count
  • result->flags |= BLOCK_NEEDS_FREE | 2Set the reference count to1(bit 1 begins with the reference count), and the flag needs to be released
  • _Block_call_copy_helper(result, aBlock);To obtaincopyFunction pointer and execute,copyThis parameter is optionalblockisGlobal blockOr the captured variable isBasic data types(for example, int),copyIs empty
  • result->isa = _NSConcreteMallocBlock;Modify theresultA type ofHeap block

_Block_call_dispose_helperFunctional analysis:

Static void _Block_call_dispose_helper(struct Block_layout *aBlock) {// Obtain the copy function if (auto *pFn = _Block_get_dispose_function(aBlock)) pFn(aBlock); // Copy if there is a copy function}Copy the code

_Block_get_copy_functionFunctional analysis:

static inline __typeof__(void (*)(void *, const void *)) _Block_get_copy_function(struct Block_layout *aBlock) { if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL; Get block descriptor1 first address void *desc = _Block_get_descriptor(aBlock); #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) { struct Block_descriptor_small *bds = (struct Block_descriptor_small *)desc; return _Block_get_relative_function_pointer( bds->copy, void (*)(void *, const void *)); } #endif // memory shift get descriptor_2 struct Block_descriptor_2 *bd2 = (struct Block_descriptor_2 *)((unsigned char *)desc + sizeof(struct Block_descriptor_1)); // copy return _Block_get_copy_fn(bd2); } static inline __typeof__(void (*)(void *, const void *)) _Block_get_copy_fn(struct Block_descriptor_2 *desc) { return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy); } #define _Block_get_function_pointer(field) \ (field)Copy the code

lldbDebug verificationblockStructure:

void (^ xqBlock)(void) = ^{ NSLog(@"xq"); }; id globalBlock = xqBlock; int a = 18; void (^ xqBlock1)(void) = ^{ NSLog(@"%d",a); }; id intBlock = xqBlock1; NSObject *obj = [[NSObject alloc]init]; void (^ xqBlock2)(void) = ^{ NSLog(@"%@",obj); }; id objcBlock = xqBlock2; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LLDB debugging * * * * * * * * * * * * * * * * * * * * * * * * * * * * * (LLDB) Po globalBlock < __NSGlobalBlock__ : 0x100004040> signature: "v8@? 0" invoke : 0x100003de0 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlock Build`__main_block_invoke) (lldb) po intBlock <__NSMallocBlock__: 0x109941570> signature: "v8@? 0" invoke : 0x100003e10 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlock Build`__main_block_invoke_2) (lldb) po objcBlock <__NSMallocBlock__: 0x1099415a0> signature: "v8@? 0" invoke : 0x100003e40 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlock Build`__main_block_invoke_3) copy : 0x100003e70 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlock Build`__copy_helper_block_e8_32s) dispose : 0x100003eb0 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlock Build`__destroy_helper_block_e8_32s)Copy the code
  • As you can see from the debugging results, blocks that capture no variables or only primitive data types do not existcopyanddisposeExists only if the object type is introducedcopyanddispose

Source code debugging:

Declare the following blocks:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc]init];
        void (^ xqBlock)(void) = ^{
            NSLog(@"%@",obj);
        };
        
        NSLog(@"block is %@", xqBlock);
    }
    return 0;
}
Copy the code

Debugging at the _Block_copy function interrupt point is as follows:

(LLDB) x/4g aBlock // Output aBlock memory information 0x7ffeefBff398:0x0000000100394060 0x00000000C2000000 0x7ffeefbff3a8: 0x0000000100003E50 0x0000000100004020 (LLDB) Po 0x0000000100394060 isa __NSStackBlock__ // Flags (LLDB) P /t 0 xc2000000 / / by the results of flags can be seen that contains 1 < < 25 with the copy and the dispose (unsigned int) $2 = 0 b11000010000000000000000000000000 Invoke (LLDB) p (BlockInvokeFunction)0x0000000100003e50 (BlockInvokeFunction) $3 = 0x0000000100003e50 (KCBlockBuild '__main_block_invoke at main.m:30) // Type Strong (LLDB) p (Block_descriptor_1*)0x0000000100004020 (Block_descriptor_1 *) $4 = 0x0000000100004020 // The value gets reserved = 0, size = 40 (lldb) p *$4 (Block_descriptor_1) $5 = (reserved = 0, Size = 40) // Memory translation get Descriptor_2 (LLDB) p (Block_descriptor_2 *)($4 + 1) (Block_descriptor_2 *) $6 = 0x0000000100004030 (LLDB) p *$6 Dispose (Block_descriptor_2) $7 = {copy = 0x0000000100003e80 (KCBlockBuild '__copy_helper_block_e8_32s at main.m) Dispose = 0x0000000100003ec0 (KCBlockBuild '__destroY_helper_block_e8_32s at main.m)} // Memory pan get Descriptor_3 (LLDB) p (Block_descriptor_3 *)($6 + 1) (Block_descriptor_3 *) $8 = 0x0000000100004040 (LLDB) P *$8 Get the signature and layout (Block_descriptor_3) $9 = (signature = "v8@? 0", layout = "")Copy the code

Illustration:

Through the above analysis, we already know the underlying results of the block, but we still know nothing about the assignment of copy, Dispose and other functions inside the block and what they do. Because copy and Dispose are determined at compile time, We can compile blocks into C++ code for analysis.

Declare the following block:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [NSObject new];
        void (^ xqBlock)(void) = ^{
            NSLog(@"%@",obj);
        };
        xqBlock();
        NSLog(@"block is %@", xqBlock);
    }
    return 0;
}

Copy the code

The clang-rewrite-objc main.m -o main.cpp compiler generates c++ code with the following main function

Int main(int argc, const char * argv[]) {/* @autoreleasepool */ {__AtAutoreleasePool __autoreleasepool; NSObject *obj = objc_msgSend)(objc_getClass("NSObject"), sel_registerName("new")); xqBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344)); xqBlock->FuncPtr(xqBlock); NSLog((NSString *)&__NSConstantStringImpl__var_folders_4d_29qjz7cx0vs3nqw8qm0_ypz0jm96y7_T_main_bff74a_mi_1, xqBlock); } return 0; }Copy the code
  • By converting outC++The code can be analyzedxqBlockinitialize__main_block_impl_0Structure pointer to, the parameters in order are__main_block_func_0__main_block_desc_0_DATAobj570425344
  • blockThe call to is actually a callFuncPtrAnd will beblockTheir own incoming

__main_block_IMPL_0 structure analysis:

struct __main_block_impl_0 { struct __block_impl impl; Struct __main_block_desc_0* Desc; struct __main_block_desc_0* Desc; // des information, including NSObject *__strong obj; Constructor __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) {// obj = _obj impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • Contains all the information about the block,isa(The runtime determines the actual type),flags.FuncPtr.Desc, as well as captured variables, throughmainThe constructor is called in the function to initialize it

__block_implStructural analysis:

struct __block_impl {
  void *isa;
  int Flags; // 标识
  int Reserved; // 预留参数
  void *FuncPtr; // 执行函数
};
Copy the code
  • withlibclosure-79In the sourceBlock_layoutThe structure is almost the same, just lessdescriptor, compiled source code,descriptorThe information is saved in the__main_block_desc_0In the structure
  • FuncPtrTo be an assignment__main_block_func_0function

__main_block_func_0Functional analysis:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *__strong obj = __cself->obj; // bound by copy
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_4d_29qjz7cx0vs3nqw8qm0_ypz0jm96y7_T_main_5ed05e_mi_0,obj);
 }
Copy the code
  • blockAs you can see, within this function, only a new pointer is pointed toobj, did not copy the address, so inblockThe captured variable cannot be assigned internally.

__main_block_desc_0Structural analysis:

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};
Copy the code
  • __main_block_desc_0_DATAInitialize the structure with the parameters in order0__main_block_impl_0The size of the structure__main_block_copy_0The function, which is zerocopyThe function,__main_block_dispose_0Function is thedisopsefunction

__main_block_copy_0 and __main_block_dispose_0 functions

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

static void __main_block_dispose_0(struct __main_block_impl_0*src){
    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
Copy the code
  • These two functions are called separatelylibclosure-79Source,_Block_object_assignFunctions and_Block_object_disposefunction

_Block_object_assignFunctional analysis:

Enum {// See function implementation for a more complete description of these fields and combinations // Common objects BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ... // block object BLOCK_FIELD_IS_BYREF = 7, // a block variable // __block BLOCK_FIELD_IS_BYREF = 8, // The on stack structure holding the __block variable // __weak Use BLOCK_FIELD_IS_WEAK = 16 only on __block objects, // declared __weak, only used in byref copy helpers Dispose with BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.}; 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)) {// Common object case BLOCK_FIELD_IS_OBJECT: /******* id object =... ; [^{ object; } copy]; ********/ // _Block_retain_object_default = fn (arc) _Block_retain_object(object); *dest = object; break; / / object block case BLOCK_FIELD_IS_BLOCK: / * * * * * * * void (^ object) (void) =... ; [^{ object; } copy]; ********/ *dest = _Block_copy(object); break; / / __block or __weak __block modified object 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; / / __block modified object is a structure type, which also have copy function will be called here, the subsequent analysis case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: /******* // copy the actual field held in the __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; __block or __weak __block modified objects are struct types, There's also the copy function will be called the 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
  • BLOCK_FIELD_IS_OBJECT, common objects or willblocktoidObject is capturedblockLater,copyThe function will call this branch, call_Block_retain_objectThe function will be calledlibobjc.A.dylibtheobjc_retainTo make the object reference count+ 1
  • BLOCK_FIELD_IS_BLOCK.blockThis branch call is executed after the object is captured_Block_copyfunction
  • BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAKorBLOCK_FIELD_IS_BYREF.__blockor__weak __blockThe variable modified byblockAfter the capturecopyFunction to perform this branch, then analyze_Block_byref_copyfunction
  • The other two branches are aimed at__blockModifier, a member of the underlying generated structure that executes this branch when copied, pointer assignment

_Block_retain_objectFunction analysis:

_Block_release_object is a global function pointer that may be assigned after the program is run, so we can print it out after the program is run as shown below:

So, we can determine that this function is the objc_retain function under the libobjc.a. dylib framework, source code is as follows:

id 
objc_retain(id obj)
{
    if (obj->isTaggedPointerOrNil()) return obj;
    return obj->retain();
}
Copy the code
  • By viewing the source code can be determined_Block_retain_objectThe captured variables are actually executed onceretain

__blockAnalysis of modified variables:

From the above analysis, we can see that the __block modified variable and the ordinary variable will perform different branches, why such a difference, next to the __block analysis

Define a __block variable and use it in a block:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSObject* objc = [NSObject alloc];
        void(^xqBlock)(void) = ^{
            NSLog(@"%@",objc);
        };
        xqBlock();
    }
    return 0;
}
Copy the code

Compile to C++ file as follows:

Int main(int argc, const char * argv[]) {/* @autoreleasepool */ {__AtAutoreleasePool __autoreleasepool; __Block_byref_objc_0 objc = {0,&objc, 33554432, sizeof(__Block_byref_objc_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"))}; xqBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &objc, 570425344)); xqBlock->FuncPtr(xqBlock); } return 0; }Copy the code
  • From the compiled C++ code,objcIt was converted to one__Block_byref_objc_0Structure, and the parameters passed in are0.The address of objc.__Block_byref_id_object_copy_131The function,__Block_byref_id_object_dispose_131Function.
  • blockInitialization willobjcThe address of the.

__Block_byref_objc_0

struct __Block_byref_objc_0 { void *__isa; // isa __Block_byref_objc_0 *__forwarding; // address to itself int __flags; // Flag bit, indicating whether there is copy, dispose function, etc., int __size; // Struct size void (*__Block_byref_id_object_copy)(void*, void*); Void (*__Block_byref_id_object_dispose)(void*); void (*__Block_byref_id_object_dispose)(void*); NSObject *__strong objc; NSObject *__strong objc; // Captured variables};Copy the code

Let’s look at the __main_block_copy_0 function at this point:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
Copy the code
  • In this case, the parameter is changed, and the third parameter is 8, so it is executed_Block_object_assignThe function will executeBLOCK_FIELD_IS_BYREFBranch, execute_Block_byref_copyfunction

_Block_byref_copy function

Before analyzing the _Block_byref_copy function, familiarize yourself with the underlying structure of the __block modified variable Block_byref

Struct Block_byref {void * __ptrAuth_objc_isa_pointer isa; // struct Block_byref *forwarding; // Point to volatile int32_t flags; // contains ref count // mark uint32_t size; / / size}; struct Block_byref_2 { // requires BLOCK_BYREF_HAS_COPY_DISPOSE BlockByrefKeepFunction byref_keep; / / = __Block_byref_id_object_copy_131, copy function BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131 dispose function}; struct Block_byref_3 { // requires BLOCK_BYREF_LAYOUT_EXTENDED const char *layout; // Captured variables};Copy the code

_Block_byref_copyFunction:

static struct Block_byref *_Block_byref_copy(const void *arg) { struct Block_byref *src = (struct Block_byref *)arg; / / __block modify SRC - > forwarding - > flags = BLOCK_BYREF_HAS_COPY_DISPOSE | BLOCK_BYREF_LAYOUT_EXTENDED | 1 < < 29 if ((SRC ->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {// SRC points to stack // struct Block_byref *copy = (struct Block_byref *)malloc(src->size); copy->isa = NULL; // byref value 4 is logical refcount of 2: One for the caller, one for the stack / / modify flags, next time no longer copy to the heap area 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; if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { // Trust copy helper to copy everything of interest // If more than one Struct Block_byref_2 *src2 = (struct Block_byref_2 *src2 = (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; Struct Block_byref_3 *src3 = (struct Block_byref_3 *src3 = (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; } // Capture external variables - memory processing - life cycle save (*src2->byref_keep)(copy, SRC); } else { // Bitwise copy. // This copy includes Block_byref_3, if any. memmove(copy+1, src+1, src->size - sizeof(*src));  } } // already copied to heap else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) { latching_incr_int(&src->forwarding->flags); } return src->forwarding; }Copy the code
  • Create an area of memory in the heapcopyAnd willsrcAssigns the value tocopy
  • src->flags & BLOCK_BYREF_HAS_COPY_DISPOSEDetermine if there isBlock_byref_2The structure, if there is going to besrcandcopyAll translationBlock_byrefThe size of the structure toBlock_byref_2, respectively getsrc2andcopy2And willsrc2thebyref_keepandbyref_destroyThe function assigns a value tocopy2.
  • src->flags & BLOCK_BYREF_LAYOUT_EXTENDEDDetermine if there islayout(i.e., members), if any, willsrc2andcopy2All translationBlock_byref_2The size of the structure toBlock_byref_3, respectively getsrc3andcopy3And willsrc3thelayoutAssigned tocopy3
  • (*src2->byref_keep)(copy, src), the callbyref_keepFunction to save the captured variable.

The __block modified variable clang, described above, compiles to a __Block_byref_objc_0 structure.

Again, review the __Block_byref_objc_0 structure

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

Byref_keep is used in the _Block_byref_copy function. __Block_byref_id_object_copy = __Block_byref_id_object_copy = __Block_byref_id_object_copy

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
Copy the code
  • dstandsrcfor_Block_byref_copyFunctioncopyandsrc, and then it shifts40Bytes, just to the added member variableobjc
  • 131 = 128 | 3 =BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT, when executed again to_Block_object_assignFunction, enterBLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECTBranch,objcPointer copy, so use__blockModify the variable inblockInternal modifications can be made

Summary: The __block modified variable is actually a Block_byref structure at the bottom level, which is triple copied as follows: _Block_copy->_Block_object_assign ->_Block_byref_copy->_Block_object_assign, respectively, copies blocks to the heap, __block variables to the heap, and Pointers to the heap.

Block release:

The block release function is _Block_release, and you can see the code in the source code as follows:

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;

    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}
Copy the code
  • _Block_call_dispose_helperThe implementation of blockdisposefunction
  • _Block_destructInstanceDestructor block

disposeFunctional analysis:

As mentioned in the analysis of copy function above, dispose function was assigned a value of __main_block_dispose_0 at compilation time, which was compiled into c++ code as follows:

Static void __main_block_dispose_0(struct __main_block_impl_0* SRC){_Block_object_dispose((void*) SRC ->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __main_block_dispose_1(struct __main_block_impl_1* SRC) { _Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/); Static void __main_block_dispose_0(struct __main_block_impl_0* SRC) { _Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/); }Copy the code
  • As you can see from the compiled function call, both are calls_Block_object_disposeDelta function, justflagsParameters vary depending on the type of variable captured.

_Block_object_disposeFunctional analysis:

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
  • _Block_object_disposeAnd analyzed above_Block_object_assignThe functions are very similar, except that we’re dealing with the opposite logic.
  • __blockModify the execution of a variable_Block_byref_releaseCall the dispose function in the body of the structure, which is actually called here, goes to the last branch, does nothing, and then releases the structure.
  • If the capture isblockObject, execute again_Block_release_object, to the capturedblockObject to release.
  • It captures ordinary object execution_Block_release_objectFunction on the captured objectrelease.

_Block_byref_releaseFunctional analysis:

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); if (latching_decr_int_should_deallocate(&byref->flags)) { if (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
  • _Block_byref_releaseThe function logic is relatively simple and is simply fetched by memory translationbyref2, the callbyref_destroyFunction, which will eventually be called_Block_object_disposeFunction. As the parameter is131 = BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT, so actually this function doesn’t do anything. And then releasebyref

_Block_release_objectFunction analysis

_Block_release_object is a global function pointer that may be assigned after the program is run, so we can print it out after the program is run as shown below:

Libobjc.a.dylib objc_release = libobjc.a.dylib objc_release = libobjc.a.dylib objc_release = libobjc.a.dylib

void 
objc_release(id obj)
{
    if (obj->isTaggedPointerOrNil()) return;
    return obj->release();
}
Copy the code

The _Block_release_object function releases the captured object

_Block_destructInstanceAnalysis:

Like _Block_release_object above, _Block_destructInstance is a global function pointer, and we can print it in the same way:

Therefore, we can confirm that this function is the objc_destructInstance function of the libobjc.a.dylib framework. The source code is as follows:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}
Copy the code
  • Looking at the source code, you can confirm that_Block_destructInstanceIt’s actually callingobjc_destructInstanceFunction ofblockTo release

conclusion

A block is actually an object at the bottom, and is classified as __NSGlobalBlock__, __NSStackBlock__, and __NSMallocBlock__ depending on whether variables are captured and copied to the heap. The internal structure of the block is determined according to the different types of captured variables. For example, when the captured type is basic data type, the block has no copy and Dispose functions. Copy is used to retain the object once if it is a normal object. _Block_copy is used to copy the block if it is a block object. Byref_keep and byref_destroy are not included in the lower-level generated structure. If __block modifier is an object, the lower-level generated structure contains byref_keep and byref_destroy, which is actually a triple copy