There are three common types of blocks

1.1 NSGlobalBlock

void(^block)(void) = ^{
    NSLog(@"hello,block");
};
NSLog(@"%@", block);

<__NSGlobalBlock__: 0x1065fa088>
Copy the code

NSGlobalBlock The global type of block that is stored in memory as a global area, in which case block takes no arguments and returns no value.

1.2 NSMallocBlock

int a = 10;
void(^block)(void) = ^{
    NSLog(@"block - %d", a);
};
NSLog(@"%@", block);

<__NSMallocBlock__: 0x600003305680>
Copy the code

NSMallocBlock Heap area type block. The heap area stored in memory. In this case, the block accesses the external variable A and copies the value of a.

1.3 NSStackBlock

 int a = 10;
void(^block)(void) = ^{
    NSLog(@"block - %d", a);
};
NSLog(@"%@", ^{
    NSLog(@"block - %d", a);
});

<__NSStackBlock__: 0x7ffeec33f518>
Copy the code

NSStackBlock is a stack block that is stored in memory. In this case, it prints a form that is not assigned to a block object. Once the assignment is complete, a copy of a is held and it becomes a heap block. Currently, there are very few blocks in development.

void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };
Copy the code
  • There are also three intermediate types of blocks at the bottom that the system handles, which are not very common and I won’t go into too much here.

Second, block circular reference solution

  • A circular reference to a block: When A calls the dealoc method, A will send A release signal to B. When B receives the release signal, B’s retainCount reference count will be -1. If B’s reference count is 0, B will call the Dealoc method to release, and THEN A will process its own release. If A and B hold each other, A cannot call its own delloc method, and B cannot receive the release signal, resulting in that both A and B cannot release. A circular reference is made when an object in a block holds the block, and a block{} in turn holds the object.
  • For example, compare these two examples:
// NSString *name = @"qianxiaomu"; self.block = ^(void){ NSLog(@"%@",self.name); }; self.block(); UIView animateWithDuration:0.25 animations:^{NSLog(@"%@",self.name); };Copy the code

2.1 weak – strong – dance

typedef void(^MuBlock)(void);

@property(nonatomic, copy) MuBlock muBlock;

__weak typeof(self) weakSelf = self;
self.muBlock = ^(void){
     __strong typeof(weakSelf) strongSelf = weakSelf;
     NSLog(@"%@",strongSelf.name);
}

self.muBlock();
Copy the code
  • Use __weak to generate a new weak reference object, weakSelf. WeakSelf and self point to the same piece of memory address, which does not increase self’s reference count (this can be verified by looking at __weak’s underlying source code and CFGetRetainCount).
  • __strong is used to extend the declaration cycle of weakSelf beyond {}. Usually, we will do time-consuming operations in block{} to extend the declaration cycle of weakSelf, which can avoid the early release of weakSelf. So __weak and __strong need to be used together.
  • Development is recommended.

2.2 Intermediary model

Manual mode

  • By manual, I mean you manage the declaration cycle of the mediator ViewController. Examples are as follows:
__block ViewController *vc = self; self.muBlock = ^(void){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",vc.name); vc = nil; // manually release}); }; self.muBlock();Copy the code

Automatic mode

  • Again, by automatic I mean give the block to manage the declaration cycle of the intermediary ViewController. It is recommended to be used in actual development, as follows:
self.muBlock = ^(ViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
    });
};

self.muBlock(self);
Copy the code

3. Clang analysis of block

vim block.c

xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c

#include "stdio.h"

int main(){
    void(^block)(void) = ^{
        printf("Mu");
    };
    return 0;
}
Copy the code
  • Open block. CPP to see the compiled results.
int main(){ void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; } static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Mu"); } void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); // Constructor block->FuncPtr(block);Copy the code

3.1 The nature of blocks

  • Block is the source equivalent of __main_block_IMPL_0, which is an anonymous function. If you search __main_block_IMPL_0, you can see that __main_block_IMPL_0 is a structure, but a block is actually an object. Block also has its own ISA.
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 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

3.2 How does a block have the ability to hold external variables

3.2.1 Common variable A

  • A simple change to the code is as follows:
int main(){
    int a = 11;
    void(^block)(void) = ^{
        printf("Mu - %d", a);
    };
    
     block();
    return 0;
}
Copy the code
  • Clang again, check source code changes:
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; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; printf("Mu - %d", a); } int main(){ int a = 11; 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
  • You can see that the __main_block_IMPL_0 function passes an argument, a
  • In the __main_block_IMPL_0 structure, the corresponding variable A is automatically generated at compile time
  • When executed, it copies its own value of A to the new a. It’s important to note that the A that we’re holding is not the same as the A that we’re holding outside.

3.2.2 __block variable A

  • Add a __block to a, and then add ++ to a in the block.
  • Re-clang, view the source code.
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; 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) {// Constructor imp.isa = &_nsConcretestackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; Static void __main_block_func_0(struct __main_block_impl_0 *__cself) {block_byref_a_0 *a = __cself->a; (a->__forwarding->a)++; printf("Mu - %d", (a->__forwarding->a)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); } int main(){ __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 11}; // the third parameter in __main_block_impl_0 &a, Void (*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; }Copy the code
  • In __main_block_IMPL_0, the address & of object A is passed as a parameter
  • External variables generate a __Block_byref_a_0 structure, which holds Pointers and values to the original variable
  • When executed, __main_block_func_0 is a pointer copy of a, holding the same a as our external A, pointing to the same memory address.

3.2.3 __block decorates the OC object NSString

  • For a new project, write the following code in main.m:
        _block NSString *Mu_name = [NSString stringWithFormat:@"mu"];
        void (^block1)(void) = ^{ // block_copy
            Mu_name = @"qianxiaomu";
            NSLog(@"Mu_Block - %@",Mu_name);
        };
        block1();
Copy the code
  • Clang 查 看 原 文 :
__Block_byref_Mu_name_0 Mu_name = {(void*)0, (__Block_byref_Mu_name_0 *) &mu_name, 33554432, sizeof(__Block_byref_Mu_name_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...) )(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_9f330d_mi_0)}; void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_Mu_name_0 *)&Mu_name, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1); Struct __Block_byref_Mu_name_0 {void *__isa; __Block_byref_Mu_name_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); // 5*8 = 40 NSString *Mu_name; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_Mu_name_0 *Mu_name; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_Mu_name_0 *_Mu_name, int flags=0) : Mu_name(_Mu_name->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_Mu_name_0 *Mu_name = __cself->Mu_name; // bound by ref (Mu_name->__forwarding->Mu_name) = (NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_9f330d_mi_1; NSLog((NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_9f330d_mi_2,(Mu_name->__forwarding->Mu_n ame)); } static void __Block_byref_id_object_copy_131(void *dst, void *src) { _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); } static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); }Copy the code
  • In contrast to variable A, __block modifies class objects, with __Block_byref_id_object_copy_131 and __Block_byref_id_object_dispose_131 at the bottom.
  • __Block_byref_id_object_copy and __Block_byref_id_object_dispose are added to the __Block_byref_ structure.
  • This involves the block’s internal copy of an object, which will be analyzed below.

Why is block() needed?

  • In the __main_block_IMPL_0 structure, the internal implementation code block of the first passed block, __main_block_func_0, is represented by a pointer *fp and then assigned to the FuncPtr property of the IMPL, via its nameless constructor, The final call is made in main and the underlying code is block->FuncPtr(block). A block of code cannot be implemented without calling block().

Four, block source code analysis

  • Preparation stage
    • Break point at block to enter assembly layer
    • Add the symbolic breakpoint objc_retainBlock to _Block_copy
    • Add the symbol breakpoint _Block_copy and you can see the underlying source of the block in libsystem_blocks. Dylib.
    • Libclosure -74 compendible source code download
  • The real type underlying block, Block_layout
struct Block_layout { void *isa; volatile int32_t flags; // contains ref count int32_t reserved; BlockInvokeFunction invoke; struct Block_descriptor_1 *descriptor; // imported variables }; // Values for Block_layout->flags to describe block objects enum { BLOCK_DEALLOCATING = (0x0001), // runtime BLOCK_REFCOUNT_MASK = (0xfffe), // runtime BLOCK_NEEDS_FREE = (1 << 24), // runtime BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code BLOCK_IS_GC = (1 << 27), // runtime BLOCK_IS_GLOBAL = (1 << 28), // compiler BLOCK_USE_STRET = (1 << 29), // compiler: undefined if ! BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), // compiler BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler }; #define BLOCK_DESCRIPTOR_1 1 struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; }; #define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 { // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy; BlockDisposeFunction dispose; }; #define BLOCK_DESCRIPTOR_3 1 struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char *signature; const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT };Copy the code
  • Isa: Class indicating block type
  • Flags: indicates the identifier. Information in the bits field similar to ISA, such as hascxx
  • Reserved: The reserved bit is used to store the internal information of a block
  • Invoke: Function pointer to the execution of a block code block
  • Descriptor: Additional information about blocks. There are three types
    • Block_descriptor_1: Must have, containing the reserved information and the size of the reserved bit
    • Block_descriptor_2: Not necessarily. See BLOCK_HAS_COPY_DISPOSE in flag, which contains Pointers to the function that blocks can copy and dispose.
    • Block_descriptor_3: Not necessarily. BLOCK_HAS_SIGNATURE and BLOCK_HAS_EXTENDED_LAYOUT in the flag contain signatures.

4.1 Three-Layer Copy of Blocks (1)

// 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; } else { // Its a stack block. Make a copy. struct Block_layout *result = (struct Block_layout *)malloc(aBlock->descriptor->size); if (! result) return NULL; memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first #if __has_feature(ptrauth_calls) // Resign the invoke pointer as it uses address authentication. result->invoke = aBlock->invoke; #endif // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1 _Block_call_copy_helper(result, aBlock); // Set isa last so memory analysis tools see a fully-initialized object. result->isa = _NSConcreteMallocBlock; return result; }Copy the code
  • The first layer copy is the _Block_copy of the block itself, which goes from stack space to heap space
  • _Block_copy source code:
    • If a block needs to be freed, it can be freed by identifying bits and operations
    • If it is a globalBlock, copy is not required and is returned directly
    • Instead of the first two cases, which are stack blocks
      • Malloc Space application result
      • Memmove copy aBlock
      • Call the pointer invoke handoff
      • The assignment of flags
      • Isa changed to _NSConcreteMallocBlock

4.2 Three-Layer Copy of Blocks (2)

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; 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 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; } (*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
  • The second copy is __block modified _Block_byref_copy, which copies the modified object to the structure type of Block_byref.
    • The pointer SRC is used to hold the passed structure data
    • If SRC is not copied
      • Apply for a new structure pointer copy with the same size as the SCR
      • Isa placeholder assignment
      • Set the flag of copy
      • The forwarding pointer of the copy pointer of the heap points to itself, and the stack pointer of SRC points to the copy pointer of the heap, so that the forwarding pointer of SRC and copy are identical. Forwarding is an object caught by __block, which is why objects caught by __block can be modified
      • If you have the BLOCK_BYREF_HAS_COPY_DISPOSE capability, obtain the pointer address of Block_byref_2 by SRC +1, essentially completing the assignment of Block_byref_2
      • If BLOCK_BYREF_LAYOUT_EXTENDED capability is available, complete the Block_byref_3 assignment of copy
      • (*src2->byref_keep)(copy, SRC) in clang layer source code is donevoid (*__Block_byref_id_object_copy)(void*, void*)In the operation. And that happens to be where the third copy comes in.
enum { // Byref refcount must use the same bits as Block_layout's refcount. // BLOCK_DEALLOCATING = (0x0001), // runtime // BLOCK_REFCOUNT_MASK = (0xfffe), // runtime BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime }; Struct Block_byref {void *isa; struct Block_byref *forwarding; volatile int32_t flags; // contains ref count uint32_t size; }; Struct Block_byref_2 {// requires struct Block_byref_2 {// requires struct Block_byref_2 {// requires struct Block_byref_2 {// requires struct Block_byref_2 BLOCK_BYREF_HAS_COPY_DISPOSE BlockByrefKeepFunction byref_keep; BlockByrefDestroyFunction byref_destroy; }; struct Block_byref_3 { // requires BLOCK_BYREF_LAYOUT_EXTENDED const char *layout; };Copy the code

4.3 Three-Layer Copy of Blocks (3)

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)) { case BLOCK_FIELD_IS_OBJECT: _Block_retain_object(object); *dest = object; break; case BLOCK_FIELD_IS_BLOCK: *dest = _Block_copy(object); break; case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: case BLOCK_FIELD_IS_BYREF: *dest = _Block_byref_copy(object); break; case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: *dest = object; break; case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK: *dest = object; break; default: break; }}Copy the code
  • The third level of copying is that the block performs a _Block_object_assign on the variables of the object passed in, and copies the variables of the object to be used inside the block into the block.
  • _Block_object_assign source code analysis
    • If the block captures a normal object, it returns itself
    • If the block is an object in the stack, _Block_copy returns the block object in the heap
    • If the captured object is decorated with __block, the struct object of _Block_byref_copy is returned
    • An object decorated with a __block is translated to the object’s variables for internal use.