This chapter begins with the analysis of “familiar” blocks, introducing the types of blocks, several solutions to Block circular reference, and how blocks operate at the bottom.

💪 Let ‘s Go

0x00 – blockThe type of

By printing, you can see that there are several types of blocks

  • __NSGlobalBlock__

A normal block, which uses no external data, is a global block

  • __NSMallocBlock__

The use of external variables will change the location of the block, called the heap block, because the block is both a function and an object, the underlying copy a

  • __NSStackBlock__

Where the local variable A is stack block before processing (before copying) and heap block after processing (after copying), the current stack block is less and less, ⚠️⚠️ the current Xcode is 11.6 iOS 13.6, if it is Xcode12 iOS14 after, The printout is NSMallocBlock

It can also be held with __weak, and blocks are also in the stack

Xcode12 iOS14 environment print

conclusion

  • blockDo not access external variables, directly inThe global area
  • If you were using external data, it would beblockCarry out correspondingcopy.
    • ifblockIs a strong reference, at this timeblockThe heap area
    • If you use__weakmodifiedblockAt this time,blockIn the stack

0x01 – Loop ♻️ reference

The most common problem with using blocks in development is the problem of circular references

  • Normal releaseObject A release is called when object A holds object BdeallocMethod, which is then sent to object BreleaseSignal, object B receives the signal, if the reference count of object B is 0, object B’s owndeallocMethod, release yourself.

  • A circular reference: Object A and object B hold each other. Therefore, object A cannot release itself and cannot send itself to object BreleaseThe signal, object B can’t get the signal, can’t release itself, keeps taking up this memory. Also calledMemory leaks


0x02 – Loop ♻️ reference resolution

When you have a problem with circular references, look for a solution:

First look at the following two pieces of code, will the sound loop reference?

/ / code
NSString *name = @"cc";
self.block = ^(void) {NSLog(@ "% @".self.name);
};
self.block();

/ / code 2
UIView animateWithDuration:1 animations:^{
    NSLog(@ "% @".self.name);
};
Copy the code
  • It’s pretty obviousCode 1A circular reference problem occurred because inblockMemory usedself, resulting inblockholdselfAnd theselfAlso heldblockSo it leads to mutual holding that cannot be released.
  • Code section 2The circular reference problem does not occur in theselfThey don’t own itanimation block, does not constitute a mutual holding relationship.

Solution:

  1. weak-strong-dance
typedef void(^testBlock)(void);

@property(nonatomic.copy) testBlock tBlock;

__weak typeof(self) weakSelf = self;
self.tBlock = ^(void) {NSLog(@ "% @",weakSelf.name);
};
Copy the code

If there is no nested block inside the block, the __weak modifier is directly used. At this time, weakSelf points to that area of self, but it does not change the reference count of that memory. WeakSelf just keeps the relation of a weak reference to that memory area.


The previous one only uses a single _weak to solve the problem, but in the actual project, there is still a drop problem, which is easy to be released in advance in some scenarios, such as:

// VC2:
__weak typeof(self) weakSelf = self;
    self.tBlock = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
Copy the code

In the code above, there is a piece of code in Vc2 that executes output after 2 seconds in the block, jumping from the previous VC1 to vC2 and returning to the previous page before the delayed output is executed.

__strong typeof(weakSelf) strongSelf = weakSelf;

StrongSelf is a temporary variable inside the block, the internal block executes, the scope is released,

This is breaking self’s strong reference to a block, depending on the mediator pattern, and is auto-set to nil, auto-release

  1. __blockModified variable mode

This approach also relies on the mediator pattern, but is changed from automatic to manual

self.name = @"cccc"; __block ViewController *vc = self; self.tBlock = ^(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.tBlock();Copy the code

Notice that the block here has to be called, if it’s not called, vc is not null, it’s still a circular reference, self and block are not released

  1. Object itself as a parameter
typedef void(^testBlock)(ViewController *);

@property(nonatomic.copy) testBlock tBlock;

    self.name = @"cccc";

    self.tBlock = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@ "% @",vc.name);
        });
    };
    self.tBlock(self);
Copy the code

The object self is supplied as an argument to the block for internal use, with no reference counting problems

  1. NSProxyVirtual class
  • OCOnly single inheritance, based onRuntime mechanism, can be accessed throughNSProxyimplementationPseudo multiple inheritanceTo fill the gap of multiple inheritance
  • NSProxyIs andNSObjectThe same level of class, it also implementsNSObjectThe agreement.
  • NSProxyIt’s actually aMessage redirection encapsulates an abstract class, like a proxy, middleware can implement message forwarding to another instance by inheriting it and overriding the following two methods
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
Copy the code

Usage scenarios

  • Realize multiple inheritance function
  • To solve theNSTimer&CADisplayLinkWhen creatingStrong reference to selfQuestion, referenceYYKittheYYWeakProxy.

Show scenes

  • Custom oneNSProxyA subclass of
@interface MyProxy: NSProxy // Two initialization methods, save an external object - (id)transformObjc:(NSObject *)objc; + (instancetype)proxyWithObjc:(id)objc; @end @interface MyProxy () @property(nonatomic, weak, readonly) NSObject *objc; @end @implementation MyProxy - (id)transformObjc:(NSObject *)objc{ _objc = objc; return self; } + (instancetype)proxyWithObjc:(id)objc{ return [[self alloc] transformObjc:objc]; } #pragma mark - //2. Invocation - (void)forwardInvocation:(NSInvocation *)invocation {SEL SEL = [invocation) selector]; if ([self.objc respondsToSelector:sel]) { [invocation invokeWithTarget:self.objc]; }} / / 1, query the signature of the method - (NSMethodSignature *) methodSignatureForSelector: (SEL) SEL {NSMethodSignature * signature; if (self.objc) { signature = [self.objc methodSignatureForSelector:sel]; } else { signature = [super methodSignatureForSelector:sel]; } return signature; } - (BOOL)respondsToSelector:(SEL)aSelector { return [self.objc respondsToSelector:aSelector]; } @endCopy the code
  • throughNSProxySubclassing implementationMultiple inheritancefunction

  • throughMyProxyTo solveA strong reference to self in the timerThe problem
self.timer = [NSTimer timerWithTimeInterval:1 target:[MyProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code

conclusion

There are essentially two types of circular reference breaks. take self –> block —-> self for example

  • breakself---->blockA strong reference to theweakEmbellish properties, but in that case,blockIt was released before the game was created, so it doesn’t work here
  • breakblock------>selfStrong referential relationships at this level,
    • weak-strong
    • __block modifier, internally empty, must call block
    • The objectselfAs a parameter
    • throughNSProxyIn the way ofself

0x03 –BlockAnalysis of the underlying

Analyze the underlying logical structure through Clang, breakpoint debugging and assembly

Write a block file first, then use Clang rewrite.

#include "stdio.h"

int main(a){
    __block int a = 10;
    char c = 'b';
    void(^block)(void) = ^ {printf("test block %d %c", a, c);
        a+=1;
    };
    block(a);return 0;
}
Copy the code

Use xcrun-sdk iphonesimulator clang-arch x86_64-rewrite-objc block.c to generate a block. CPP file. Block is compiled at the bottom to the following form

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("test block");
}
int main(a){
    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;
}
/ / * * * * * * simplify the * * * * * *
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));// constructor

block->FuncPtr(block);// The block call executes
Copy the code
  • First of all to see__main_block_impl_0, a structure type, descriptionblockIt’s also an object, and whyblockYou can use% @Reasons for printing. And no references to external variables, itsimpl.isa = &_NSConcreteStackBlock;, is a block in the stack (in this case, copy to the heap at run time).
//** The structure type of the block **
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; }};//** Block structure type **
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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)};

    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);

Copy the code

Summary: The essence of a block is an object, a function, and a structure. Since a block function has no name, it is also called an anonymous function, similar to c/ C ++ function Pointers

Diagram of the underlying source code structure for Clang compilation


1. blockWhy is it called?

The underlying block is compiled into a struct __main_block_IMPL_0, and when initialized,

The first argument is the block of code that the block needs to execute, namely {…….. } Function address of the curly braces. *fp, assigned to impl.FuncPtr = fp

Then call block->FuncPtr in main

  • Function declarationThe internal implementation of a block is declared as a function__main_block_func_0
  • Perform specific function implementations: by calling a blockFuncPtrPointer to call block execution

2. How does block get external variables?

int main(a){
    int a = 11;
    void(^block)(void) = ^ {printf("tsetBlock - %d", a);
    };
    
     block(a);return 0;
}
Copy the code

The above code snippet is compiled to the bottom layer by Clang as follows

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;// The corresponding variables are automatically generated at compile time
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;// Block isa defaults to stackBlockimpl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  // bound by copy: a = 10, a is not the same as the a passed in __cself
	int a = __cself->a; 
  printf("tsetBlock - %d", a);
}
    
int main(a){

    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

The ain __main_block_IMPL_0 exists as a copy of the value. If a is changed internally, the compiler does not allow this because a is read-only and prompts __block modifier.

Conclusion:

When a block captures an external variable, it automatically generates the same attribute internally to store it


3 __blockThe principle of

  • Copy the value- Deep copy, only copies the value, and the copy value cannot be changed, pointing to different memory space, in the case of ordinary variablesaisCopy the value
  • Pointer to the copy– Shallow copy. The generated objects point to the same memory space__blockModified variableaIt’s a pointer copy
  • As you can see from the above, the variable A is decorated with __block in the code, and is encapsulated as an object in the underlying Clang compilation

  • Then, in main, the address of the encapsulated object is passed to the constructor

  • The processing of A inside __main_block_func_0 is a pointer copy, in which the created object A points to the same memory space as the passed object A

struct __Block_byref_a_0 {//__block decorates the structure of an external variable
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {// Block structure type
  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) {// constructorimpl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {// Block internal implementation
  __Block_byref_a_0 *a = __cself->a; // a copy of the bound by ref pointer that points to the same address space as the __cself object
        // is equivalent to a++
        (a->__forwarding->a)++;
        printf("CJL - %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(a){
    //__Block_byref_a_0 is a struct, and a equals the assignment of the struct, that is, enclosing the external variable a as an object
    //&a is the address of the external variable A
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0.sizeof(__Block_byref_a_0), 11};
    // The third argument in __main_block_impl_0 &a is the address of the encapsulated object 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

conclusion

  • External variablesuse__blockThe modifier produces one__Block_byref_a_0The structure of the body
  • The structure is usedSaves the pointer and value of the original variable
  • To generate a variableThe pointer address of the structure object is passed to the blockYou can then operate on external variables inside the block

4 BlockThe underlying true type

Let’s first explore where Block is in the underlying source code.

  • Through theBlockAt the break point, openAlways Show DisassemblySee the assembly

  • See there areobjc_retainBlockAdd this symbol breakpoint to continue debugging

  • add_Block_copySymbol breakpoint, see debugging


Clang-compiled CPP static files also have this sentence

You can download libclosure-74 from Apple’s open source website, and check the _Block_copy implementation to see that the underlying block type is Block_layout

BlockExact type

// Block structure
struct Block_layout {
    // Points to a class that indicates the block type
    void *isa;/ / 8 bytes
    // Used as an identifier, similar to the bit field in ISA, which represents some additional block information in bits
    volatile int32_t flags; // Contains ref count 4 bytes
    // Reserved information, which can be understood as the reserved position, is used to store information about internal variables of the block
    int32_t reserved;/ / 4 bytes
    // A function pointer to the calling address of the specific block implementation
    BlockInvokeFunction invoke;
    // Additional information about the block
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
Copy the code
  • isa: points to a class that indicates the block type
  • flagsThe flags identifier represents additional information about blocks in bits, similar to the bit fields in ISA. Flags are of the following typesBLOCK_HAS_COPY_DISPOSEBLOCK_HAS_SIGNATURE.BLOCK_HAS_COPY_DISPOSETo decide whether or notBlock_descriptor_2.BLOCK_HAS_SIGNATURETo decide whether or notBlock_descriptor_3
    • 1 a –BLOCK_DEALLOCATING– BLOCK_NEEDS_FREE is used for bitwise operations and is passed along with Flags to indicate that the block can be freed.
    • 16 – lowerBLOCK_REFCOUNT_MASK, stores the value of the reference count; Is an optional parameter
    • 24th –BLOCK_NEEDS_FREE, low 16 is a valid flag, according to which the program decides whether to increase or decrease the value of the reference count bit;
    • 25 –BLOCK_HAS_COPY_DISPOSE, whether a copy helper function is available;
    • 26 –BLOCK_IS_GC, whether there is a block destructor;
    • 27th, indicating whether there is garbage collection; //OS X
    • 28 –BLOCK_IS_GLOBALIs a global block;
    • 30th –BLOCK_HAS_SIGNATURE, as opposed to BLOCK_USE_STRET, judgeWhether the current block has a signature. Used for dynamic invocation at Runtime.
/ / flags logo
// Values for Block_layout->flags to describe block objects
enum {
    // The free flag is used for BLOCK_BYREF_NEEDS_FREE and is passed along with flags to indicate that the block can be freed
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // Stores the value of the reference reference count, which is an optional parameter
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    // Indicates whether the lower 16 bits are valid, which the program uses to decide whether to increase or decrease the value of the reference count bit
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    // Whether the copy helper function is available, (a copy helper function) determines block_description_2
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    // whether a block C++ destructor is available
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    // Flag if there is garbage collection, OSX
    BLOCK_IS_GC =             (1 << 27), // runtime
    // Whether the flag is a global block
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    // In contrast to BLOCK_HAS_SIGNATURE, check whether the current block has a signature that can be invoked dynamically at Runtime
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if ! BLOCK_HAS_SIGNATURE
    // Whether there is a signature
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    // Use expanded, decide block_description_3
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
Copy the code
  • reserved: Reserved information: reserved information, understood as some reserved locations, presumably used to store information about variables in the block
  • invoke: function pointer, pointing toBlockThe block of function code to execute
  • descriptor:blockAdditional information, such as the number of variables to keep, the size of the block, and the pointer to the auxiliary function to copy or dispose. Have three kinds of
    • Block_descriptor_1Is choice
    • Block_descriptor_2andBlock_descriptor_3It’s optional. Configure these two according to some conditions
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;// Keep the information
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;// Copy the function pointer
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;/ / signature
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Copy the code

In Runtime. h, get Block_descriptor_1 directly from the Block_layout structure, Both Block_descriptor_2 and Block_descriptor_3 are obtained by the memory translation size of Block_descriptor_1.

static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
    return aBlock->descriptor;// Prints by default
}
#endif

//Block description: copy and dispose functions
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;/ / descriptor_1 address
    desc += sizeof(struct Block_descriptor_1);// Get by memory translation
    return (struct Block_descriptor_2 *)desc;
}

// Block description: signature related
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}
Copy the code

Memory changes

void (^block1)(void) = ^ {NSLog(@"testBlock");
};
block1();
Copy the code

The code snippet above

A breakpoint at a block reads the value of a register, and the block is global

  • blockInternal uses external variables
int a = 10;
void (^block1)(void) = ^ {NSLog(@"testBlock - %d", a);
};
block1();
Copy the code

Breakpoint stops at objc_retainBlock, look at the memory value type before entering, now stack block

After entering objc_retainBlock, print the type again as stack block

Then skip to _Block_copy, place the breakpoint at the end, and let _Block_copy run to see if the value has changed

The value becomes mallocBlcok, and the address changes accordingly, mainly because the block address has been changed to a heap block

Calling process

X11 added 0x10 from X9 (16 in decimal), and then BLR X11 jumped to execute. _block_invoke was called from the read X11. Memory translation was also used to obtain the invoke in block_layout, which was preceded by 16 bytes. So we add 0x010.

// Block structure
struct Block_layout {
    // Points to a class that indicates the block type
    void *isa;/ / 8 bytes
    // Used as an identifier, similar to the bit field in ISA, which represents some additional block information in bits
    volatile int32_t flags; // Contains ref count 4 bytes
    // Reserved information, which can be understood as the reserved position, is used to store information about internal variables of the block
    int32_t reserved;/ / 4 bytes
    // A function pointer to the calling address of the specific block implementation
    BlockInvokeFunction invoke; / / 8 bytes
    // Additional information about the block
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
Copy the code

Functions that are actually executed after coming in from X11.

From the source code of the Block_layout structure mentioned above, it can be seen that there is an attribute called invoke, that is, the executor of the block, which is transferred to invoke from isa’s first address translation of 16 bytes, and then executes the call

The signature

To proceed, read register X0 to get Block_descriptor_1 by translation 24, where Descriptor_3 has the block’s signature

Continue to check whether descriptor2 is present by flags and above BLOCK_HAS_COPY_DISPOSE = (1 << 25)

Flags and BLOCK_HAS_SIGNATURE = (1 << 30) check whether descriptor3 is present,

Continue printing the signature:

The signature block

// No return value
return value: -------- -------- -------- --------
    type encoding (v) 'v'
    flags {}
    modifiers {}
    frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
    memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
    //encoding = (@), type @?
    type encoding(@) '@? '//@ is isObject,? IsBlock stands for isBlockObject
    flags {isObject, isBlock}
    modifiers {}
    frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
    // The offset is 8 bytes
    memory {offset = 0, size = 8}
Copy the code

The block signature information is similar to the method signature information. It mainly shows the block return value, parameters, and types

_Block_copyAnalysis of the

Go to _Block_copy source:

// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
Stack block -> heap 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;// Forcibly convert to Block_layout to prevent external impact
    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 it is a global block, return it directly
        return aBlock;
    }
    else {// Stack block or heap block, because the heap needs to allocate memory, so only stack block
        // Its 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 it
        if(! result)return NULL;
        // Copy aBlock to result via memmove memory copy
        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;// Invoke can be invoked directly
#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;// Set block object type to heap block
        returnresult; }}Copy the code
  • Fault-tolerant processing,argDoes not exist, returnNULL
  • throughflagsDetermine whether to release the device directly
  • If it isglobal block, directly return, need copy
  • The other two cases areStack blockandHeap blockBecause theHeap area blockYou need to open up memory space in memory, there’s no memory opening code in front of it, so it has to be a stack block, as the comment saysIts a stack block. Make a copy
    • Apply for memory space to store the stackblcok
    • throughmemmovewillblockCopy to the newly created memory
    • Set up theblocktheisafor_NSConcreteMallocBlock

_Block_object_assignAnalysis of the

Analyze _Block_object_assign, of which BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF are the most commonly used

// The type of external variables that Block captures
// 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
    // Plain object, i.e. no other reference type
    BLOCK_FIELD_IS_OBJECT   =  3.// id, NSObject, __attribute__((NSObject)), block, ...
    // Block as a variable
    BLOCK_FIELD_IS_BLOCK    =  7.// a block variable
    // A variable decorated with __block
    BLOCK_FIELD_IS_BYREF    =  8.// the on stack structure holding the __block variable
    //weak Weak reference variable
    BLOCK_FIELD_IS_WEAK     = 16.// declared __weak, only used in byref copy helpers
    // The returned call object - handles an extra flag added to the internal object memory of block_byref, used in conjunction with flags
    BLOCK_BYREF_CALLER      = 128.// called from __block (byref) copy/dispose support routines.
};
Copy the code

In the underlying compiled code, _Block_object_assign is the method that is called when external variables are copied

// Clang compiles CPP files
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

In _Block_object_assign,

  • If it isBLOCK_FIELD_IS_OBJECTNormal object, to the systemARCProcess, copy object pointer, point to the same memory space, memory reference count +1, so external reference variables cannot be freed

case BLOCK_FIELD_IS_OBJECT: 
	 _Block_retain_object(object);
	*dest = object;
        break;

static void _Block_retain_object_default(const void *ptr __unused) { }// Do nothing
Copy the code
  • If it isBLOCK_FIELD_IS_BLOCK, the block type is passed_Block_copyAction to remove a block fromThe stack is copied to the heap, analyzed above
case BLOCK_FIELD_IS_BLOCK:
	*dest = _Block_copy(object);
        break;
Copy the code
  • If it is__block modification, that is,BLOCK_FIELD_IS_BYREFTo call_Block_byref_copyMethod for memory copy and general processing
 case BLOCK_FIELD_IS_BYREF:
 *dest = _Block_byref_copy(object); // Copy the variables of the current structure
        break;
Copy the code

Go to the _Block_byref_copy method

static struct Block_byref* _Block_byref_copy(const void *arg) {
    
    // Force the Block_byref structure type and save a copy
    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;
        // The Block_byref inside a block holds the same object as the Block_byref outside. This is why __block modifiers are modifiable
        // The address pointer of copy and SCR has reached a perfect copy of the same address pointer
        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 you have copy capability
        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 is a struct, __block may modify objects, which are saved by byref_keep and called when appropriate
            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 an incoming object toBlock_byrefType, save a copy
  • A chunk of memory is reallocated according to the size of the incoming object
  • If it has already been copied, processing returns
  • srcandcopytheforwardingThe pointer is pointing to the same piece of memory, so this is__blockModify the reason why a value can be modified.

The key methods are executed in the following order: _Block_copy -> _Block_byref_copy -> _Block_object_assign, which corresponds to the preceding three-layer copy

Three-layer copy summary

So, to sum up, a three-layer copy of a block refers to the following three layers:

  • [Layer 1] Through_Block_copyImplementing objectTheir own copyFrom stack to heap
  • [Second layer] Pass_Block_byref_copyMethod to copy the object toBlock_byrefStructural type
  • [Third layer] call_Block_object_assignMethods,__blockModification of theCopy of the current variable

Note: Block copy has three levels only for __block modified objects

_Block_object_disposeAnalysis of the

Like retain and release, blocks have _Block_object_assign, as well as _Block_object_dispose

// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// To help dispose of the contents When Blocks or Block_byrefs holds an object, its destruction helper routine calls this entry point to help dispose of the contents
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:// a __block modified variable of type bref
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:// Block variable
        _Block_release(object) ;
        break;
      case BLOCK_FIELD_IS_OBJECT:// Common objects
        _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
  • Enter the_Block_byref_releaseSource code, is mainly the release of objects, variables destroyed
static void _Block_byref_release(const void *arg) {
    // The object is forcibly converted to a Block_byref structure
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;// Cancel the pointer reference
    
    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) {// Whether there is a copy helper function
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);// Destroy the copy object
            }
            free(byref);/ / release}}}Copy the code

see

Blocks Programming Topics

Block test

Welcome big brother message correction 😄, code word is not easy, feel good to give a thumbs-up 👍 have any expression or understanding error please leave a message; Common progress;