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
block
Basic 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
block
The 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
- variable
a
bexqBlock
Capture, but due to use__weak
Modifier 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
- variable
a
bexqBlock
Capture, 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 heap
block
The actual is__NSMallocBlock__
Class, whose inheritance chain is__NSMallocBlock__
->__NSMallocBlock
->NSBlock
->NSObject
. - The other types
block
With the heapblock
Similarly, 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
- object
objc
The reference count after initialization is1
whenobjc
bexqBlock
After capture, the reference count becomes2
, because it is__NSMallocBlock__
Automatically copied to the heap, so the reference count plus one becomes3
xqBlock1
To capture theobjc
The reference count increment becomes4
, because it is__NSStackBlock__
, so it is not copied to the heapxqBlock2
isxqBlock1
Copy 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 to
weakPerson
Point to theperson
In theblock
Internal useweakPerson
Ok, to avoidperson
Being released prematurely causes a block to executeperson
Is empty, can be inblock
Internal use of one__strong
Declare a local variable pairweakPerson
Make 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 thatblock
Executed when copied_Block_copy
function
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
flags
Flag 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_copy
Function 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 releaseif (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 obtainblock
The size of thestruct Block_layout *result = (struct Block_layout *)malloc(size);
Open up in the heap areasize
Size of memory space and assign to resultmemmove(result, aBlock, size);
willaBlock
Copy toresult
Memory spaceresult->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
Reset reference countresult->flags |= BLOCK_NEEDS_FREE | 2
Set 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 obtaincopy
Function pointer and execute,copy
This parameter is optionalblock
isGlobal block
Or the captured variable isBasic data types
(for example, int),copy
Is emptyresult->isa = _NSConcreteMallocBlock;
Modify theresult
A type ofHeap block
_Block_call_dispose_helper
Functional 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_function
Functional 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
lldb
Debug verificationblock
Structure:
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 exist
copy
anddispose
Exists only if the object type is introducedcopy
anddispose
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 out
C++
The code can be analyzedxqBlock
initialize__main_block_impl_0
Structure pointer to, the parameters in order are__main_block_func_0
,__main_block_desc_0_DATA
,obj
,570425344
block
The call to is actually a callFuncPtr
And will beblock
Their 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, throughmain
The constructor is called in the function to initialize it
__block_impl
Structural analysis:
struct __block_impl {
void *isa;
int Flags; // 标识
int Reserved; // 预留参数
void *FuncPtr; // 执行函数
};
Copy the code
- withlibclosure-79In the source
Block_layout
The structure is almost the same, just lessdescriptor
, compiled source code,descriptor
The information is saved in the__main_block_desc_0
In the structure FuncPtr
To be an assignment__main_block_func_0
function
__main_block_func_0
Functional 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
block
As you can see, within this function, only a new pointer is pointed toobj
, did not copy the address, so inblock
The captured variable cannot be assigned internally.
__main_block_desc_0
Structural 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_DATA
Initialize the structure with the parameters in order0
,__main_block_impl_0
The size of the structure__main_block_copy_0
The function, which is zerocopy
The function,__main_block_dispose_0
Function is thedisopse
function
__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_assign
Functions and_Block_object_dispose
function
_Block_object_assign
Functional 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 willblock
toid
Object is capturedblock
Later,copy
The function will call this branch, call_Block_retain_object
The function will be calledlibobjc.A.dylib
theobjc_retain
To make the object reference count+ 1
BLOCK_FIELD_IS_BLOCK
.block
This branch call is executed after the object is captured_Block_copy
functionBLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK
orBLOCK_FIELD_IS_BYREF
.__block
or__weak __block
The variable modified byblock
After the capturecopy
Function to perform this branch, then analyze_Block_byref_copy
function- The other two branches are aimed at
__block
Modifier, a member of the underlying generated structure that executes this branch when copied, pointer assignment
_Block_retain_object
Function 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_object
The captured variables are actually executed onceretain
__block
Analysis 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,
objc
It was converted to one__Block_byref_objc_0
Structure, and the parameters passed in are0
.The address of objc
.__Block_byref_id_object_copy_131
The function,__Block_byref_id_object_dispose_131
Function. block
Initialization willobjc
The 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_assign
The function will executeBLOCK_FIELD_IS_BYREF
Branch, execute_Block_byref_copy
function
_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_copy
Function:
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 heap
copy
And willsrc
Assigns the value tocopy
src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE
Determine if there isBlock_byref_2
The structure, if there is going to besrc
andcopy
All translationBlock_byref
The size of the structure toBlock_byref_2
, respectively getsrc2
andcopy2
And willsrc2
thebyref_keep
andbyref_destroy
The function assigns a value tocopy2
.src->flags & BLOCK_BYREF_LAYOUT_EXTENDED
Determine if there islayout
(i.e., members), if any, willsrc2
andcopy2
All translationBlock_byref_2
The size of the structure toBlock_byref_3
, respectively getsrc3
andcopy3
And willsrc3
thelayout
Assigned tocopy3
(*src2->byref_keep)(copy, src)
, the callbyref_keep
Function 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
dst
andsrc
for_Block_byref_copy
Functioncopy
andsrc
, and then it shifts40
Bytes, just to the added member variableobjc
131
=128 | 3
=BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT
, when executed again to_Block_object_assign
Function, enterBLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT
Branch,objc
Pointer copy, so use__block
Modify the variable inblock
Internal 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_helper
The implementation of blockdispose
function_Block_destructInstance
Destructor block
dispose
Functional 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_dispose
Delta function, justflags
Parameters vary depending on the type of variable captured.
_Block_object_dispose
Functional 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_dispose
And analyzed above_Block_object_assign
The functions are very similar, except that we’re dealing with the opposite logic.__block
Modify the execution of a variable_Block_byref_release
Call 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 is
block
Object, execute again_Block_release_object
, to the capturedblock
Object to release. - It captures ordinary object execution
_Block_release_object
Function on the captured objectrelease
.
_Block_byref_release
Functional 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_release
The function logic is relatively simple and is simply fetched by memory translationbyref2
, the callbyref_destroy
Function, which will eventually be called_Block_object_dispose
Function. As the parameter is131 = BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT
, so actually this function doesn’t do anything. And then releasebyref
_Block_release_object
Function 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_destructInstance
Analysis:
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_destructInstance
It’s actually callingobjc_destructInstance
Function ofblock
To 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