IOS underlying principles + reverse article summary

This article mainly introduces block types, circular reference solutions and block underlying analysis

Block type

There are three main types of blocks

  • __NSGlobalBlock__: Global block, stored in the global area
void(^block)(void) = ^{
    NSLog(@"CJL");
};
NSLog(@"%@", block);
Copy the code

In this case, the block has no parameters and no return value, and belongs to the global block

  • __NSMallocBlock__: Heap block, because block is both a function and an object
int a = 10;
void(^block)(void) = ^{
    NSLog(@"CJL - %d", a);
};
NSLog(@"%@", block);
Copy the code

In this case, the block accesses the external variable, that is, the underlying copy of a, so it is a heap block

  • __NSStackBlock__Block: the stack area
 int a = 10;
void(^block)(void) = ^{
    NSLog(@"CJL - %d", a);
};
NSLog(@"%@", ^{
    NSLog(@"CJL - %d", a);
});
Copy the code

Where the local variable A is before processing (that is, before copying)The stack area block, after processing (that is, after copying) isHeap area blockThere are fewer and fewer blocks in the stack

In this case, you can go through__weakWithout a strong hold, a block remains a block on the stack

conclusion

  • Blocks are stored directly in the global area

  • If a block accesses an external variable and copies the block accordingly, it is called copy

    • If the block at this point is a strong reference, the block is stored in the heap, known as the heap block

    • If the block at this point becomes a weak reference via __weak, the block is stored in the stack, known as the stack block

Block circular reference

  • Normal release: when A holds A reference to B. When A calls the dealloc method, it sends A release signal to B. B receives the release signal, and B’s dealloc method is called if B’s retainCount (reference count) is 0

  • Circular reference: A and B hold each other, so A cannot call the dealloc method to send A release signal to B, and B cannot receive A release signal. So neither A nor B can be released at this point

See the figure below

Resolving circular references

Do the following two pieces of code have circular references?

NSString *name = @"CJL"; self.block = ^(void){ NSLog(@"%@",self.name); }; self.block(); UIView animateWithDuration:1 animations:^{NSLog(@"%@",self.name); };Copy the code

One kind of circular reference occurs in the code, because the external variable name is used inside the block, causing the block to hold self, which originally held the block, so self and the block hold each other. In code 2, there is no circular reference. Although external variables are used, self does not hold the bblock of the animation. Only animation holds self, which does not constitute mutual holding

There are several common ways to solve circular references;

  • 【 答 案 】 Weak -strong-dance

  • 【 method 2 】__block decorates an object (note that the object needs to be empty inside the block, and the block must be called)

  • 【 Method 3 】 Pass the object self as the parameter of the block, and provide the inner use of the block

  • 【 Method 4 】 Use NSProxy

Weak -stong-dance

  • If there is no nested block inside the block, use it directly__weakI’ll just modify self
typedef void(^CJLBlock)(void);

@property(nonatomic, copy) CJLBlock cjlBlock;

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

At this timeweakSelfselfPointing to the same pieceMemory spaceAnd the use of__weak does not cause self's reference count to change, which can be verified by printing the pointer address of weakSelf and Self, as well as the reference count of Self, as shown below

  • If a block is nested within a block, use both blocks__weak__strong
__weak typeof(self) weakSelf = self;
self.cjlBlock = ^(void){
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.name);
    });
};
self.cjlBlock();
Copy the code

StrongSelf is a temporary variable in the scope of the cjlBlock, that is, when the inner block executes, strongSelf is released

This is part of breaking self’s strong reference to the block, depending on the mediator pattern, and part of automatically setting to nil, which is automatically freeing

Method two: __block modifies variables

This approach, which also relies on the intermediary mode, is a manual release, and is made by decorating objects with __block, mainly because __block-decorated objects can be changed

__block ViewController *vc = self; self.cjlBlock = ^(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.cjlBlock();Copy the code

The thing to notice here is that the block has to be called, if you don’t call the block, vc will not be empty, so it’s still a circular reference, neither self nor block will be released

Method three: take the object self as an argument

The main thing is to use the object self as an argument and provide it for use inside the block without reference counting problems

typedef void(^CJLBlock)(ViewController *);

@property(nonatomic, copy) CJLBlock cjlBlock;

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

The fourth method of circular reference, using the NSProxy virtual class, is described below

NSProxy virtual class

  • OC is a single inheritance language, but it is based on the runtime mechanism, so NSProxy can be used to implement pseudo-multiple inheritance, filling in the gap of multiple inheritance

  • NSProxy and NSObject are a class of the same class, a virtual class, if you will, that implements NSObject’s protocol

  • NSProxy is actually an abstract class that encapsulates message redirection, similar to a proxy, middleware, which can be inherited and override the following two methods to implement message forwarding to another instance

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
Copy the code

Usage scenarios

There are two main scenarios for using NSProxy

  • implementationMultiple inheritancefunction
  • To solve theNSTimer&CADisplayLinkWhen creatingStrong reference to selfQuestion, referenceYYKittheYYWeakProxy.

Circular reference solution principle

Self is replaced by a custom object of the NSProxy class, and methods are used to forward messages

The following is an implementation of the NSProxy subclass and a scenario for using it

  • Customize oneNSProxyA subclass ofCJLProxy
@interface CJLProxy : NSProxy - (id)transformObjc:(NSObject *)objc; + (instancetype)proxyWithObjc:(id)objc; @end @interface CJLProxy () @property(nonatomic, weak, readonly) NSObject *objc; @end @implementation CJLProxy - (id)transformObjc:(NSObject *)objc{ _objc = objc; return self; } + (instancetype)proxyWithObjc:(id)objc{ return [[self alloc] transformObjc:objc]; } //2. (void)forwardInvocation (NSInvocation *) {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
  • The customCatClasses andDogclass
/ / * * * * * * * * the Cat class * * * * * * * * @ interface Cat: NSObject @ end @ implementation Cat - (void) eat {NSLog (@ "cats eat fish"); } @ the end / / Dog class * * * * * * * * * * * * * * * * @ interface Dog: NSObject @ end @ implementation Dog - (void) shut {NSLog (@ "Dog"); } @endCopy the code
  • This is implemented through CJLProxyMultiple inheritancefunction
- (void)cjl_proxyTest{
    Dog *dog = [[Dog alloc] init];
    Cat *cat = [[Cat alloc] init];
    CJLProxy *proxy = [CJLProxy alloc];
    
    [proxy transformObjc:cat];
    [proxy performSelector:@selector(eat)];
    
    [proxy transformObjc:dog];
    [proxy performSelector:@selector(shut)];
}
Copy the code
  • This is solved through CJLProxyA strong reference to self in the timerThe problem
self.timer = [NSTimer timerWithTimeInterval:1 target:[CJLProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code

conclusion

There are basically two solutions for looping applications. Take self -> block -> self as an example

  • To break self’s strong reference to a block, you can use the block attribute modifier weak, but this will cause the block to be released every time it is created, so breaking a strong reference from here doesn’t work

  • To break the block’s strong reference to self, this is to communicate between self’s scope and the block scope. The communication can be agents, values, notifications, parameters, and so on. The common solutions are as follows:

    • weak-strong-dance

    • __block(The object inside the block is left empty and block is called)

    • Take the object self as an argument to the block

    • Replace self with a subclass of NSProxy

Block underlying analysis

Mainly through clang, breakpoint debugging and other methods to analyze the Block bottom

nature

  • defineblock.c file
#include "stdio.h"

int main(){

    void(^block)(void) = ^{
        printf("CJL");
    };
    return 0;
}
Copy the code
  • throughxcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.cTo compile block.c intoblock.cpp, where the block is compiled at the bottom to the following form
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("CJL"); } / / * * * * * * simplify void * * * * * * block (*) (void) = __main_block_impl_0 (__main_block_func_0, & __main_block_desc_0_DATA)); // Constructor block->FuncPtr(block); // Block calls executionCopy the code

Block equals __main_block_IMPL_0, which is a function

  • To view__main_block_impl_0, it is aThe structure of the bodyWhich also shows that the block is a__main_block_impl_0Type of object. Why is thatblockTo be able to% @Reasons for printing
//** 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

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

The relationships between the clang-compiled source blocks are shown below__blockFor example, the modified variable

1. Why do blocks need to be called

In the underlying block’s type __main_block_IMPL_0 structure, created by its constructor of the same name, the first passed inner implementation block, __main_block_func_0, denoted by FP, is then assigned to the impl’s property FuncPtr, This is then called in main, which is why block needs to be called. If not called, the block of code implemented inside the block will not be executed, which can be summed up in the following two points

  • Function declaration: The inner implementation of the block is declared as a function __main_block_func_0

  • Execute the specific function implementation: Call the block execution by calling the FuncPtr pointer to the block

2. How does a block obtain an external variable

  • Define a variable and call it in a block
int main(){
    int a = 11;
    void(^block)(void) = ^{
        printf("CJL - %d", a);
    };
    
     block();
    return 0;
}
Copy the code
  • The underlying compilation looks like this
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; Void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) a(_a) { impl.isa = &_NSConcreteStackBlock; // The default ISA for a block is stackBlock imp. Flags = Flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; Printf ("CJL - %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

The ain __main_block_func_0 is a copy of the value. If a++ is used in the internal implementation of the block, it will cause compiler ambiguity, i.e. the a is read-only

Summary: When a block captures an external variable, it automatically generates the same property inside to save it

The principle of __block

  • rightaAdd a__blockAnd then do a in the block++operation
int main(){

    __block int a = 11;
    void(^block)(void) = ^{
        a++;
        printf("CJL - %d", a);
    };
    
     block();
    return 0;
}
Copy the code
  • The underlying compilation is as follows
    • The A in main is an object encapsulated by an external variable

    • In __main_block_IMPL_0, we give the address &a of object a to the constructor

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

Struct __Block_byref_a_0 {// void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; Struct __main_block_impl_0 {// Block 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) {&_nsConcretestackBlock = &_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++ (a->__forwarding->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(){//__Block_byref_a_0 is a struct, a is equal to the struct assignment, The external variables are encapsulated into a object / / & a is outside the address of the 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 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

Conclusion:

  • External variables generate the __Block_byref_a_0 structure

  • The structure is used to hold Pointers and values to the original variables

  • We pass the address of the pointer to the struct object generated by the variable to the block, and then we can operate on external variables from inside the block

The two copies are compared as follows

  • Value copy – deep copy, where only the value is copied, and the copied value cannot be changed, pointing to a different memory space. In this case, the common variable A is value copy

  • Pointer copy – shallow copy, where the generated objects point to the same memory space. In this case, the __block-modified variable A is a pointer copy

Block The underlying true type

Analyze the location of the block source code

  • Analyze the block at runtime by breaking the point at the block

  • addobjc_retainBlockSign break point, find that it’s going to go to_Block_copy

  • add_Block_copySign breakpoint, run break, atlibsystem_blocks.dylibIn the source

You can download the latest libclosure-74 source code from apple’s open source website. By looking at the source code implementation of _Block_copy, you can find that the underlying block type is Block_layout

Block true type

View the definition of the Block_layout type, which is a structure

Struct Block_layout {// point to class void *isa; //8 bytes // Used as an identifier, similar to the bit field in ISA, which represents additional information about some block in bit bytes volatile INT32_t flags; // contains ref count 4 bytes // Reserved information, can understand the reserved position, used to store the variable information inside the block int32_t reserved; //4 bytes // function pointer to the specific block implementation call address BlockInvokeFunction; Struct Block_descriptor_1 *descriptor; // imported variables };Copy the code
  • isa: Points to the class that indicates the block type
  • flags: an identifier that represents additional information about some block in bit bits, similar to the bit fields in ISA, where there are several kinds of flags, mainly focusing onBLOCK_HAS_COPY_DISPOSEBLOCK_HAS_SIGNATURE.BLOCK_HAS_COPY_DISPOSEDecide if there isBlock_descriptor_2.BLOCK_HAS_SIGNATUREDecide if there isBlock_descriptor_3
    • The first bit, -block_deallocating, frees the mark, typically uses the BLOCK_NEEDS_FREE bit and operation, passing in Flags, to tell the block to be freed.

    • The lower 16 bits -block_refcount_mask, which stores the value of the reference count; Is an optional parameter

    • Bit 24 – BLOCK_NEEDS_FREE, lower than 16 is a valid flag that the program uses to determine whether to increase or decrease the value of the reference bit;

    • 25th – BLOCK_HAS_COPY_DISPOSE, whether a copy helper function is available;

    • Bit 26 – BLOCK_IS_GC, whether it has a block destructor;

    • 27th, indicating whether there is garbage collection; //OS X

    • The 28th bit -block_is_global indicates whether the block is a global block.

    • Bit 30 – BLOCK_HAS_SIGNATURE, opposite to BLOCK_USE_STRET, determines whether the current block has a signature. For dynamic invocation at Runtime.

// CJL // Values for Block_layout->flags to describe block objects enum {// Mark = (0x0001), // runtime // stores the reference reference count value, Is an optional parameter BLOCK_REFCOUNT_MASK = (0xfffe), // runtime // whether the lower 16 bits are valid, BLOCK_NEEDS_FREE = (1 << 24), // Runtime // has a copy helper function, Block_description_2 BLOCK_HAS_COPY_DISPOSE = (1 << 25), Block ++ destructor BLOCK_HAS_CTOR = (1 << 26), // compiler: Helpers have C++ code. OSX BLOCK_IS_GC = (1 << 27), // runtime // whether the flag is global block BLOCK_IS_GLOBAL = (1 << 28), BLOCK_USE_STRET = (1 << 29); // Compiler: undefined if ! BLOCK_HAS_SIGNATURE = (1 << 30), // compiler // Uses extension, Decide block_description_3 BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler};Copy the code
  • Reserved: Reserved information. It can be understood that the reserved position is used to store information about variables inside the block

  • Invoke: is a function pointer that points to the block’s execution code

  • Descriptor: Additional information about the block, such as the number of reserved variables, the size of the block, and the pointer to the helper function that is copied or disposed. Have three kinds of

    • Block_descriptor_1Is choice
    • Block_descriptor_2Block_descriptor_3It’s all optional
#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 { // 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

The descriptors of the descriptors are shown in the constructor, where Block_descriptor_2 and Block_descriptor_3 are translated from the address of Block_descriptor_1

static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock) { return aBlock->descriptor; } #endif // CJL 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; // Address of descriptor_1 desc += sizeof(struct Block_descriptor_1); Return (struct Block_descriptor_2 *)desc; struct Block_descriptor_2 *)desc; } // CJL 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

  • Interrupt point run, walk toobjc_retainBlock, read register x0 at the breakpointblockGlobal block, i.e.,__NSGlobalBlock__type

  • Add the external variable a and print it inside the block
int a = 10;
void (^block1)(void) = ^{
    NSLog(@"CJL - %d", a);
};
block1();
Copy the code

X0 — stack block — at the block breakpoint is read__NSStackBlock__

  • Execute to a symbolic breakpointobjc_retainBlockWhen, it is still the stack block

  • increase_Block_copyAdd a breakpoint to the last ret, read x0, and find the path_Block_copyAfter that, it becomesHeap block, i.e.,__NSMallocBlock__, mainly becauseBlock addressHas changed for the heap block

Call the situation

  • You can also verify with breakpoints

– Register read x0 read x0 for heap block-register read x9 read x9 (image-b14a19-1605377133592)

-register read x11, which points to a memory space for storing '_block_invoke'! [image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ae71004f958489ba47da3f72e3d9333~tplv-k3u1fbpfcp-zoom-1.image)Copy the code
  • Hold down thecontrol + step intoAnd into the_block_invokeIt can be concluded that yesTranslation memoryThe resulting internal implementation of the block

AforementionedBlock_layoutStructure source code, from the source code can be seen, there is a propertyinvokeThat is, the executor of the block is fromisaThe first address of is shifted16Bytes toinvokeAnd then execute the call

The signature

  • Go ahead and readx0Register, look at the memory layout, by memory translation 3*8 to get the Block_layout propertiesdescriptor, mainly to see if there isBlock_descriptor_2Block_descriptor_3, where 3 contains the signature of block
    • register read x0 , read register X0
    • po 0x00000002828a2160, the printing block,
    • x/8gx 0x00000002828a2160, that is, print the block memory information

x/8gx 0x00000001008a0010To view the memory of descriptor, where the third0x000000010089f395Said the signature

  • Determine whether Block_descriptor_2, that is, whether the BLOCK_HAS_COPY_DISPOSE (copy helper) of flags has a value

    • p/x 1<<25, that is, 1 moves 25 digits to the left. Its hexadecimal value is 0x2000000
    • P 0x02000000 & 0x00000000C1000002, that isBLOCK_HAS_COPY_DISPOSE & flagsTheta is equal to 0, which means noneBlock_descriptor_2

  • Check whether Block_descriptor_3 exists

    • p/x 1<<30That is, 1 moves 30 bits to the left
    • p 0x40000000 & 0x00000000c1000002, i.e.,BLOCK_HAS_SIGNATURE & flagsIf there is a value, there isBlock_descriptor_3

    • p (char *)0x000000010089f395– access toBlock_descriptor_3The properties in thesignatureThe signature

    • po [NSMethodSignature signatureWithObjCTypes:"v8@?0"], that is, print the signature

The signature part is described as follows

// 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 is @? type encoding (@) '@? '//@ is isObject,? Is isBlock, {} frame {offset = 0, offset adjust = 0, size = 8 Memory {offset = 0, size = 8} memory {offset = 0, size = 8}Copy the code

The signature information of a block is similar to the signature information of a method, mainly reflecting the return value, parameter, and type of the block

Block Analyzes copies three times

_Block_copy Source analysis

  • Enter the_Block_copyCopy the block from the stack to the heap
    • If it needs to be released, if it needs to be released directly

    • If it’s globalBlock — no copy needed, just return it

    • Stack block or heap block, stack block, stack block

      • Malloc allocates memory space for receiving blocks

      • Use memmove to copy the block to the newly allocated memory

      • Set the type of the block object to heap-block, that is, result->isa = _NSConcreteMallocBlock

// Copy, or bump refcount, of a block. If really copying helper If present. // CJL Void * _block (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 an object of type Block_layout, If (aBlock->flags & BLOCK_NEEDS_FREE) {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 {// it's a stack block. Make a copy. // It's a stack block. 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; 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_object_assign analysis

To analyze a block’s three-layer copy, you first need to know what kinds of external variables are available. The most commonly used are BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF

// CJL 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 BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block,... BLOCK_FIELD_IS_BYREF = 8, BLOCK_FIELD_IS_BYREF = 8, BLOCK_FIELD_IS_WEAK = 16, // declared __weak, // declared __weak Call objects that are used in byref copy helpers - handle an extra mark in the object memory inside block_BYref, BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.};Copy the code

While _Block_object_assign is in the underlying compiled code, it is the method that is called when the external variable is copied

  • Enter the_Block_object_assignThe source code
    • If it is a normal object, it is given to the system ARC, and the pointer to the object is copied, i.e. the reference count +1, so that the external variables cannot be freed

    • If it is a variable of type block, the block is copied from the stack to the heap using the _Block_copy operation

    • If the variable is __block, call _Block_byref_copy for memory copy and general processing

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; // The inner block holds the same Block_byref object as the outer block holds the same Block_byref object. This is why __block-modified variables can be modified. // Copy and SCR address Pointers are perfectly identical copies. 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; } __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
  • Enter the_Block_byref_copyThe source code
    • Convert the incoming object to a Block_byref struct object and save a copy

    • No external variables are copied to the heap. Memory needs to be allocated. It is copied

    • If it has been copied, it is processed and returned

    • The forwarding Pointers to copy and SRC both point to the same piece of memory. This is why __block-decorated objects can be modified

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; // The inner block holds the same Block_byref object as the outer block holds the same Block_byref object. This is why __block-modified variables can be modified. // Copy and SCR address Pointers are perfectly identical copies. 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; } __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

Code debugging

  • To define a__blockModification of theNSStringobject
__block NSString *cjl_name = [NSString stringWithFormat:@"CJL"]; void (^block1)(void) = ^{ // block_copy lg_name = @"CJL"; NSLog(@"CJL - %@",lg_name); // block memory}; block1();Copy the code
  • The xcRun compilation results are as follows,
    • The compiled cjl_name is more than the normal variable __Block_byref_id_object_copy_131 and __Block_byref_id_object_dispose_131

    • The __Block_byref_cjl_name_0 structure contains __Block_byref_id_object_copy and __Block_byref_id_object_dispose

/ / * * * * * * * * compiled cjl_name * * * * * * * * __Block_byref_cjl_name_0 cjl_name = {(void *) 0, (__Block_byref_cjl_name_0 *) & cjl_name, 33554432, sizeof(__Block_byref_cjl_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)}; / / * * * * * * * * __Block_byref_cjl_name_0 structure * * * * * * * * struct __Block_byref_cjl_name_0 {void * __isa; __Block_byref_cjl_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 *cjl_name; }; / / * * * * * * * * __Block_byref_id_object_copy_131 * * * * * * * * / / block their own copies (_Block_copy) - __block bref structure copy (_Block_object_assign) Static void __Block_byref_id_object_copy_131(void * DST,) -- copy the external variable (stored in bref) to memory in _Block_object_assign Void * SRC) {// DST external captured variables, that is, the structure - 5*8 = 40, _Block_object_assign((char*) DST + 40, *(void * *) ((char*) SRC + 40), 131); } //********__Block_byref_id_object_dispose_131******** static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); }Copy the code
  • throughlibclosure-74Compilable source breakpoint debugging, the key method execution order is:_Block_copy -> _Block_byref_copy -> _Block_object_assign, exactly corresponds to the above three layers of copy

How does the block get cjL_name?

  • 1. Copy the block to the heap using the _Block_copy method

  • 2. Copy it properly using the _Block_object_assign method, because the underlying element of __block is the Block_byref structure

  • 3. Find that there is an object in the external variable, take the corresponding object cjl_name from breF, copy it to the block space, and then use it (the same space can be used, different cannot be used). *dest = object; * block_object_assign; *dest = object; See)

Three layer copy summary

So, in summary, a three-tier copy of a block refers to the following three layers:

  • [Layer 1] Copy the object itself from the stack to the heap using _Block_copy

  • [Layer 2] Using the _Block_byref_copy method, copies the object to the Block_byref structure type

  • Call the _Block_object_assign method to make a copy of the __block-modified current variable

Note: The copy of a block has three layers only for __block-decorated objects

_Block_object_dispose analysis

The _Block_object_object method is dispose method, which is used to dispose of the block. Perform different release operations

// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point // to help dispose of When Blocks or Block_byrefs hold objects, Its destruction assistant routine will call this entry point to help dispose of the contents 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://__block modifier, // Get rid of the __block data structure held in a Block _Block_byref_release(object); break; Case BLOCK_FIELD_IS_BLOCK://block type variable _Block_release(object); break; Case BLOCK_FIELD_IS_OBJECT:// Common 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
  • Enter the_Block_byref_releaseSource code, the main object, the release of variables to destroy
Static void _Block_byref_release(const void *arg) {Block_byref *byref = (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) {// Whether copy AIDS are available struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1); (*byref2->byref_destroy)(byref); } free(byref); // Release}}}Copy the code

Therefore, to sum up, the three-layer copy process of Block is shown in the figure below