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 nowaAnd beforeaThe 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 = &_NSConcreteStackBlockThat nowThe stack typeThis is going to be, based on our previous analysisHeap blockWhy 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 golibobjcGo 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_copyThe implementation of the function is inlibsystemLibrary, this library is not open source, let’s find a replacement librarylibclosureSource code analysis. We are inlibclosureSource 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_copyMake a break point at the end:

At this timeblockThe 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.

    • throughmallocAllocates memory space for receiving blocks
    • Use remove to copy blocks to the newly allocated memory
    • Set the block object type to heap block. willisaPoint to the__NSConcreteMallocBlock
_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__blockModify the variable called_Block_byref_copyFunction 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 inBlock_byrefStructure 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__blockDecorated objects have the ability to modify.

Three layers of copy summary

  • Level 1: Pass_Block_copyImplementing objectTheir own copyFrom stack to heap
  • Layer 2: Pass_Block_byref_copyMethod to copy the object toBlock_byrefStructural type
  • Third time: Call_Block_object_assignMethods,__blockModification of theCopy of the current variable