Block is a structure we often encounter in development, and in this article we’ll explore its structure.
The classification of the block
The classification of blocks is already very clear, I believe, into global blocks, heap blocks and stack blocks. Let’s do an example to see the difference
-
Global block
void (^block)(void) = ^{ }; NSLog(@"%@",block); // Print the result <__NSGlobalBlock__: 0x1072d4100>Copy the code
A global block is a block that does not capture any external variables, but only uses static and global variables, stored in the global area of memory.
-
Heap block
int a = 10; void (^block)(void) = ^{ NSLog(@"Cooci - %d",a); }; NSLog(@"%@",block); <__NSMallocBlock__: 0x60000130C4B0 >Copy the code
The heap block captures external variables and stores them in the heap area of memory.
-
Stack block
int a = 10; void (^__weak block)(void) = ^{ NSLog(@"Cooci - %d",a); }; NSLog(@"%@",block); // Print the result <__NSStackBlock__: 0x7ffee06ac4d8>Copy the code
Stack blocks also capture external variables. The difference between stack blocks and heap blocks is the __weak modifier, which is stored in the stack area of memory
A circular reference to a block
Block causes a circular reference
A and B hold each other, so A cannot call dealloc method to send release signal to B, and B cannot receive release signal either. So neither A nor B can be released. As is shown in
Resolving circular references
Let’s look at a piece of code that references the loop
NSString *name = @"JS";
self.block = ^(void){
NSLog(@"%@",self.name);
};
self.block();
Copy the code
Loop references occur in this code, because the external variable name is used inside the block, causing the block to hold self, which originally held the block, and thus self and the block to hold each other.
Solutions:
-
__weak and __strong are used together
typedef void(^JSBlock)(void); @property(nonatomic, copy) JSBlock jslBlock; __weak typeof(self) weakSelf = self; self.jslBlock = ^(void){ __strong typeof(weakSelf) strongSelf = weakSelf; NSLog(@"%@",weakSelf.name); } self.jslBlock(); Copy the code
This is the easiest way to think about it, using __weak to break strong references and __strong to release self prematurely, while the block executes without retrieving the value because self has been released.
-
__block defines a temporary variable pointing to self.
__block ViewController *vc = self; self.jslBlock = ^(void){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",vc.name); vc = nil; }); }; self.jslBlock();Copy the code
This is done by defining a variable outside the method that points to self, trapping temporary variables inside the block, setting the temporary variable to nil at the end of use, and adding __block because it needs to be nulled in the block’s memory.
-
Block plus one argument, using argument
typedef void(^JSBlock)(ViewController *); @property(nonatomic, copy) JSBlock jslBlock; self.jslBlock = ^(ViewController *vc){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",vc.name); }); }; self.jslBlock(self); Copy the code
The underlying analysis of blocks
We analyzed it mainly through clang and breakpoint debugging.
Xcrun compiles and analyzes
Let’s start by customizing a block.c file
#include "stdio.h"
int main(){
int a = 18;
void(^block)(void) = ^{
printf("js - %d",a);
};
block();
return 0;
}
Copy the code
Compile block.c to block. CPP using the xcrun command. xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("js - %d",a);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
int a = 18;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
Copy the code
Let’s remove the type coercion code:
int main(){
int a = 18;
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block->FuncPtr(block);
return 0;
}
Copy the code
After simplification, we see that the block code block is a __main_block_IMPL_0 structure, the __main_block_IMPL_0 structure is a structure, and its IMPL is a structure
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; /// struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };Copy the code
Note: The external variable A is captured inside the structure and a corresponding member variable A is generated inside the structure.
Let’s make a change to the code:
int main(){
__block int a = 18;
void(^block)(void) = ^{
a++;
printf("js - %d",a);
};
block();
return 0;
}
Copy the code
Re-xcrun to see the results:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
- And you can see that now
a
And beforea
The difference is that it’s added__Block_byref_a_0 *
Modifier, so that the captured variable can be modified, passed to the block is the address of A, so that the block can be modified inside. impl.isa = &_NSConcreteStackBlock
That nowThe stack type
This is going to be, based on our previous analysisHeap block
Why is it different?- Fp is a functional save that will not be executed if not called.
Source code analysis
We start with assembly, see which library the source code is in, and we break the point
And then look at the assembly code,
We add symbolic breakpointsobjc_retainBlock
:
So let’s golibobjc
Go to searchobjc_retainBlock
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
Copy the code
_Block_copy is actually called, and no implementation of the method is found in the libobJC library, so we continue with the symbolic breakpoint:
_Block_copy
The implementation of the function is inlibsystem
Library, this library is not open source, let’s find a replacement librarylibclosure
Source code analysis. We are inlibclosure
Source search_Block_copy
:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present. void *_Block_copy(const void *arg) { struct Block_layout *aBlock; if (! arg) return NULL; // The following would be better done as a switch statement aBlock = (struct Block_layout *)arg; if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; Make a copy. Size_t size = Block_size(aBlock); 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); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1 _Block_call_copy_helper(result, aBlock); // Set isa last so memory analysis tools see a fully-initialized object. result->isa = _NSConcreteMallocBlock; return result; }}Copy the code
The Block_layout structure is as follows:
struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // Isa flags are stack and heap block types volatile int32_t flags; // contains ref count reference count int32_t reserved; // Process data BlockInvokeFunction invoke; Struct Block_descriptor_1 *descriptor; // Imported variables};Copy the code
With a brief overview of the structure, we can hit a symbolic breakpoint to see the structure of the block in action.
objc_retainBlock
Notice that the block type is stillStackBlock
.
- in
_Block_copy
Make a break point at the end:
At this timeblock
The type of__NSMallocBlock__
Type.
BlockLayout structure
struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // Isa flags are stack and heap block types volatile int32_t flags; // contains ref count reference count int32_t reserved; // Process data BlockInvokeFunction invoke; Struct Block_descriptor_1 *descriptor; // Imported variables};Copy the code
If we look at the Block_descriptor_1 structure, we find that there is no signature information printed above.
We look at the source code found:
#define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; // Copy function pointer BlockDisposeFunction dispose; }; #define BLOCK_DESCRIPTOR_3 1 struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char *signature; // Sign const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT}Copy the code
Block_descriptor_2 and Block_descriptor_3 are optional, they are obtained by the Block_descriptor_1 memory translation.
Capture a copy of a variable
_Block_copy
// Copy, or bump refcount, of a block. If really copying, Call the copy helper if present. // stack Block -> stack Block 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; // Force a Block_layout object, If (aBlock->flags & BLOCK_NEEDS_FREE) {// Whether to release // latches on high latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) {// If the block is global, return aBlock; } else {// it's a stack block, it's a stack block. Make a copy. It's a stack block block, copy. struct Block_layout *result = (struct Block_layout *)malloc(aBlock->descriptor->size); // Request space and receive if (! result) return NULL; // Copy aBlock to result 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; / / can tune up directly invoke # endif / / reset refcount result - > flags & = ~ (BLOCK_REFCOUNT_MASK | BLOCK_DEALLOCATING); / / XXX not men told that can release the result - > flags | = BLOCK_NEEDS_FREE | 2; // logical refcount 1 _Block_call_copy_helper(result, aBlock); // Set isa last so memory analysis tools see a fully-initialized object. result->isa = _NSConcreteMallocBlock; // Set block object type to heap block return result; }}Copy the code
_Block_copy basically copies blocks from the stack to the heap
-
If you need to release, release it directly
-
If globalBlock does not require copy, return
-
There are two remaining cases: heap block and stack block. Since a heap block requires memory allocation, it can only be a stack block from here to here.
- through
malloc
Allocates memory space for receiving blocks - Use remove to copy blocks to the newly allocated memory
- Set the block object type to heap block. will
isa
Point to the__NSConcreteMallocBlock
- through
_Block_object_assign
Let’s look at the definition of an enumeration:
// 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 // Common objects, That is, no other reference types BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block... BLOCK_FIELD_IS_BYREF = 8, 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 // // called from __block (byref) copy/dispose support routines.};Copy the code
The most commonly used are BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF.
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) {// 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 caller, one for stack copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4; // Block_byref inside a block holds the same object as Block_byref outside, which is why __block modifiers can be modified // Copy and SCR address Pointers make perfect copies of each other, 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 field shows up in a byref block this is wrong XXX //Block_byref_2 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) { 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; } // this is equivalent to __Block_byref_id_object_copy (*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
_Block_object_assign is the method called when external variables are copied in the underlying compiled code.
- If it is a normal object, hand it to arc, copy the object pointer, reference technique +1, and the external variable cannot be released.
- If the variable is of block type, copy the block from the stack to the heap using the _Block_copy operation.
- If it is
__block
Modify the variable called_Block_byref_copy
Function for memory copy and general processing.
_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) {// 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 caller, one for stack copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4; // Block_byref inside a block holds the same object as Block_byref outside, which is why __block modifiers can be modified // Copy and SCR address Pointers make perfect copies of each other, 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 field shows up in a byref block this is wrong XXX //Block_byref_2 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) { 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; } // this is equivalent to __Block_byref_id_object_copy (*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
- Force to an object passed in
Block_byref
Structure type object. save - If the variable is not copied to the heap, memory is allocated for copying
- If it has already been copied, it is processed and returned
- The forwarding Pointers of copy and SRC point to the same piece of memory. That’s why
__block
Decorated objects have the ability to modify.
Three layers of copy summary
- Level 1: Pass
_Block_copy
Implementing objectTheir own copy
From stack to heap - Layer 2: Pass
_Block_byref_copy
Method to copy the object toBlock_byref
Structural type - Third time: Call
_Block_object_assign
Methods,__block
Modification of theCopy of the current variable