Tag: block Type of block Circular reference solution block underlying analysis


This chapter mainly introduces

1. The type of block

2. Circular reference solution

3. Underlying analysis of block


1 block type

There are three main types of blocks

1.1 __NSGlobalBlock__: global block, stored in a global area

void(^block)(void) = ^ {NSLog(@"ypy");
};
NSLog(@ "% @", block);
Copy the code

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

1.2 __NSMallocBlock__: heap block, because block is both a function and an object

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

The block accesses an external variable, the underlying copy of A, so it’s a heap block

 int a = 10;
void(^block)(void) = ^ {NSLog(@"ypy - %d", a);
};
NSLog(@ "% @"The ^ {NSLog(@"ypy - %d", a);
});
Copy the code

Where the local variable A is a stack block before processing (before no copy), and a heap block after processing (after copy). Currently, there are fewer and fewer stack blocks

1.3 __NSStackBlock__Block: the stack area

int a = 10;
    void(^ __weak block)(void) = ^ {NSLog(@"ypy - %d", a);
    };
    NSLog(@ "% @", block);
Copy the code

Strong holding can be disabled by __weak, and a block is still a stack block

conclusion

  • Blocks are stored directly inThe global area
  • ifBlock accesses external variablesAnd copy blocks
    • If at this pointBlocks are strong referencesThe block,Stored in the heap area, that is, the heap block
    • If at this pointA block becomes a weak reference by __weakThe block,Stored in the stack area, that is, the stack block

2. Block circular reference

  • Normal releaseWhen A calls the dealloc method and sends A release signal to B, B receives the release signal. If B’s retainCount (reference count) is 0, B’s dealloc method will be called
  • A circular reference: A and B hold each other, so A cannot call dealloc method to send release signal to B, and B cannot receive release signal either. So neither A nor B can be released

Do the following two pieces of code have circular references?

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

/ / code 2
UIView animateWithDuration:1 animations:^{
    NSLog(@ "% @".self.name);
};
Copy the code

In code one, a circular reference occurs, because the external variable name is used inside the block, causing the block to hold self, which originally held the block, and thus self and the block to hold each other. There is no circular reference in code 2. Although external variables are also used, self does not hold the animation block. Only the animation holds self, which does not constitute mutual holding

There are several common ways to resolve circular references.

2.1 Solving circular References

  • [Method 1]weak-strong-dance
  • 【 Method 2 】__blockModify objects (note that this is required inside blocksemptyObject, andBlock must call)
  • 【 Mode 3 】 PassRecursion object selfAs a block,parameterFor internal use within blocks
  • [Mode 4] UseNSProxy

2.1.1 weak-stong-dance

  • If there is no nested block inside the block, use it directly__weakYou can just modify self
typedef void(^YPYBlock)(void);

@property(nonatomic.copy) YPYBlock ypyBlock;

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

At this time, weakSelf and self point to the same piece of memory space, and the use of __weak will not lead to the change of self’s reference count, which can be verified by printing the pointer addresses of weakSelf and self and self’s reference count.

  • If a block contains nested blocks, use both__weak 和 __strong
__weak typeof(self) weakSelf = self;
self.ypyBlock = ^(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.yppBlock();
Copy the code

StrongSelf is a temporary variable in the scope of ypyBlock, which releases strongSelf when the inner block is finished executing

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

2.1.2 __block decorates a variable

This approach, which also relies on the mediator pattern, is manual release and modifies objects with __block, mainly because __block modifies objects that can be changed

__block ViewController *vc = self;
self.ypyBlock = ^(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.ypyBlock();
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

2.1.3 Object self as the argument

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

typedef void(^YPYBlock)(ViewController *);

@property(nonatomic.copy) YPYBlock ypyBlock;

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

2.1.4 NSProxy Virtual Class

  • OCIs the onlySingle inheritanceLanguage, but it isRuntime-based mechanismsSo we can passNSProxyTo implement thePseudo multiple inheritanceTo fill the gap of multiple inheritance
  • NSProxy 和 NSObjectIt’s a class of the same class, or oneVirtual class, just implements the protocol of NSObject
  • 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`

Usage scenarios

There are two scenarios for using NSProxy

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

Circular reference solution principle

This is mainly done by replacing self with a custom NSProxy class object and using methods to implement message forwarding

The following is an implementation of the NSProxy subclass and the scenario in which it is used

  • Custom oneNSProxyA subclass ofYPYProxy
@interface YPYProxy : NSProxy

- (id)transformObjc:(NSObject *)objc;

+ (instancetype)proxyWithObjc:(id)objc;

@end

@interface YPYProxy(a)

@property(nonatomic.weak.readonly) NSObject *objc;

@end

@implementation YPYProxy

- (id)transformObjc:(NSObject *)objc{
   _objc = objc;
    return self;
}

+ (instancetype)proxyWithObjc:(id)objc{
    return  [[self alloc] transformObjc:objc];
}

//2. The method implementation is called once the method signature is in place
- (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];
}

@end
Copy the code
  • The customCatClasses andDogclass
/ / * * * * * * * * the Cat class * * * * * * * *
@interface Cat : NSObject
@end

@implementation Cat
- (void)eat{
   NSLog(@" Cats eat fish");
}
@end

/ / Dog class * * * * * * * * * * * * * * * *
@interface Dog : NSObject
@end

@implementation Dog
- (void)shut{
    NSLog(@ "dog");
}
@end
Copy the code
  • Use YPYProxyMultiple inheritancefunction
- (void)yyy_proxyTest{
    Dog *dog = [[Dog alloc] init];
    Cat *cat = [[Cat alloc] init];
    YPYProxy *proxy = [YPYProxy alloc];
    [proxy transformObjc:cat];
    [proxy performSelector:@selector(eat)];
    [proxy transformObjc:dog];
    [proxy performSelector:@selector(shut)];
}
Copy the code
  • Use YPYProxyA strong reference to self in the timerThe problem
self.timer = [NSTimer timerWithTimeInterval:1 target:[YPYProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
 [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code

2.2 summarize

There are basically two solutions to looping applications, self -> block -> self for example

  • breakThe self to blockYou can use the block attribute modifier weak, but this will cause the block to be freed as soon as it is created, so breaking strong references from here is not an option
  • breakBlock of the selfStrong references to self and block scopescommunication, communication withProxy, pass value, notification, pass parameterAnd other ways to solve the cycle, the common solutions are as follows:
    • weak-strong-dance
    • __block(Object inside block is empty and block is called)
    • The objectselfAs a block,parameter
    • throughNSProxySubclass ofself

3. Block underlying analysis

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

3.1 nature

  • defineblock.cfile
#include "stdio.h"
int main(){
    void(^block)(void) = ^{
        printf("ypy");
    };
    return 0;
}
Copy the code
  • throughxcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c, compile block.c toblock.cpp, where the block is compiled at the bottom to the following form
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;
}

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("ypy");
}

/ / * * * * * * 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

Block = __main_block_IMPL_0, which is a function

  • To view__main_block_impl_0, it is aThe structure of the bodyBlock is a block__main_block_impl_0Type of object, that’s why, rightblockTo be able to% @Reasons for printing
//** 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;
};
Copy the code

Summary: The essence of a block is an object, a function, or a structure. Since a block function has no name, it is also called an anonymous function

The relationship between block source code compiled by Clang is shown below, using a variable decorated with __block as an example

3.2 Why does a block need to call *

In the underlying block’s __main_block_IMPL_0 structure, created by its constructor of the same name, the first passed block’s internal implementation code block, __main_block_func_0, is expressed as FP, and then assigned to the FuncPtr property of the IMPL, The call is then made in main, which is why the block needs to be called. If not called, the code block implemented inside the block will not execute, which can be summarized as follows

  • 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

3.3 How does a block get an external variable

  • Define a variable and call it in a block
int main(){
    int a = 11;
    void(^block)(void) = ^{
        printf("ypy - %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;// 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) {
  int a = __cself->a; // bound by copy: a = 10; a is not the same as __cself's a

        printf("ypy - %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, and if you do a++ inside a block implementation, it is problematic and will cause confusion in the compiler code that a is read-only

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

3.4 The principle of __block

  • rightaAdd a__block, and then on a in the block++operation
int main(){

    __block int a = 11;
    void(^block)(void) = ^{
        a++;
        printf("ypy - %d", a);
    };
    
     block();
    return 0;
}
Copy the code
  • The underlying compilation is as follows
    • In the mainaIs encapsulated by external variablesobject
    • __main_block_impl_0,Object aThe address of the&aTo the constructor
    • in__main_block_func_0The internal processing of A isPointer to the copy, the created object A and the passed object APointing to the same memory space
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("ypy - %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, 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 variablesWill be generated__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

The two copies are compared as follows

  • 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

3.5 Block Underlying type

Analyze the location of block source code

  • Analyze run-time blocks by breaking points at them

  • Add objc_retainBlock symbol breakpoint and find _Block_copy

  • Add _Block_copy symbol breakpoint, run to break, in libsystem_blocks. Dylib source code

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

Block true type

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

// 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_DISPOSE 和 BLOCK_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.
// Comment: flags flags
// 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. It can be understood that the reserved position is used to store information about variables inside the block
  • invoke: is a function pointer to the block’s executing code
  • descriptor: Additional information about a block, such as the number of variables to keep, the size of the block, and Pointers to auxiliary functions to copy or dispose. Have three kinds of
    • Block_descriptor_1Is choice
    • Block_descriptor_2 和 Block_descriptor_3It’s all optional
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;// Keep the information
    uintptr_t size;/ / block 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

The above descriptors can be seen in their constructors, where Block_descriptor_2 and Block_descriptor_3 are obtained by translation of the Block_descriptor_1 address

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

3.6 Memory Changes

  • Run at the break point, go to objc_retainBlock, and read register X0 at the block breakpoint, where the block is a global block, that is, __NSGlobalBlock__ class

  • Add the external variable A and print it inside the block

int a = 10;
void (^block1)(void) = ^ {NSLog(@"ypy - %d", a);
};
block1();
Copy the code

X0 — stack block — __NSStackBlock__ is read at the block breakpoint

  • Execute to symbol breakpointobjc_retainBlock, or stack bock

  • Add _Block_copy symbol breakpoint and break it, add a breakpoint directly to the last ret, read x0, find after _Block_copy, changed to heap block, that is __NSMallocBlock__, mainly because the block address changed to heap block

Call the situation

  • This can also be verified using breakpoints

    • Register read x0 Reads x0 as a heap block

    • Register read X9 Reads x9

    • Register read x11, which points to a memory space where _block_INVOK is stored

  • Press control + step into to enter _block_invoke, and you can get the source code of the block structure to realize the aforementioned Block_layout by memory translation. It can be seen from the source code that there is an attribute invoke, that is, the executor of the block, The invocation is executed by fetching 16 bytes from isa’s first address pan to invoke

3.7 the signature

  • Go ahead, read the X0 register, look at the memory layout, and get the property descriptor Block_layout by translation 3*8, mainly to see if there is Block_descriptor_2 and Block_descriptor_3, Three of them have block’s signature

    • Register read x0, read register x0

    • Po 0x0000000281f90000 prints the block

    • X /8gx 0x0000000281F90000, that is, block memory information is printed

    • X /8gx 0x00000001020F0068, view the memory information of descriptor, where the third 0x00000001020EF640 indicates the signature

  • Check whether Block_descriptor_2 is present, that is, whether BLOCK_HAS_COPY_DISPOSE (copy helper function) of flags has a value

    • P /x 1<<25, that is, 1 moved 25 bits left and was 0x2000000 in hex

    • P 0x02000000&0x00000000C1000002, that is, BLOCK_HAS_COPY_DISPOSE & flags, equals 0, indicating no Block_descriptor_2

  • Check whether Block_descriptor_3 is present

    • P /x 1<<30, that is, 1 moves 30 bits to the left

    • P 0x40000000&0x00000000C1000002, that is, BLOCK_HAS_SIGNATURE & flags, indicates that Block_descriptor_3 is present

    • P (char *) 0x00000001020EF640 — Get the attribute signature in Block_descriptor_3

    • Po [NSMethodSignature signatureWithObjCTypes:”v8@?0″], that is, print signatures

The signature part is described as follows

// 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

3.8 Block Copy analysis for three times

3.8.1 _Block_copy source code analysis

  • Enter the_Block_copySource code, copy block from stack to heap
    • If you need to release it, release it if you need to
    • If it isglobalBlockCopy is not required, return directly
    • Stack block or heap block; stack block or heap blockThe stack area block.
      • throughmallocAllocates memory space for receiving blocks
      • throughmemmoveCopies blocks to the newly allocated memory
      • Set the block object type to heap block, i.eresult->isa = _NSConcreteMallocBlock
// 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

3.8.2 _Block_object_assign analysis

To analyze the three-tier copy of a block, 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

// 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

The _Block_object_assign method is called when external variables are copied in the underlying compiled code

  • Enter the_Block_object_assignThe source code
    • If it is a common object, hand it toSystem ARC processingAnd,Copy object pointer, i.e.,Reference count +1, so external variables cannot be released
    • If it isBlock typeIs passed_Block_copyAction to remove a block fromThe stack is copied to the heap
    • If it is__block modificationIs called_Block_byref_copyFunction for memory copy and general processing
//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
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:
        /******* id object = ... ; [^{ object; } copy]; * * * * * * * * /

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /******* void (^object)(void) = ... ; [^{ object; } copy]; * * * * * * * * /

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; * * * * * * * * /

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; * * * * * * * * /

        *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:
        /******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; * * * * * * * * /

        *dest = object;
        break;

      default:
        break; }}Copy the code
  • Enter the_Block_byref_copyThe source code
    • Force to an object passed inBlock_byrefStructure type object, save a copy
    • External variables are not copied to the heap, so memory needs to be allocated and copied
    • If it has already been copied, it is processed and returned
    • The forwarding Pointers to copy and SRC point to the same piece of memory, which is why __block objects can be modified
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

Code debugging

  • To define a__blockModification of theNSStringobject
 __block NSString *ypy_name = [NSString stringWithFormat:@"ypy"];
void (^block1)(void) = ^ {// block_copy
    lg_name = @"ypy";
    NSLog(@"ypy - %@",ypy_name);
    
    / / block of memory
};
block1();
Copy the code
  • Xcrun compiles as follows,
    • The compiledypy_nameMore than ordinary variables__Block_byref_id_object_copy_131 和 __Block_byref_id_object_dispose_131
    • __Block_byref_ypy_name_0I have a lot of structures__Block_byref_id_object_copyand__Block_byref_id_object_dispose
//******** Compiled ypy_name********
 __Block_byref_ypy_name_0 ypy_name =
        {(void*)0,
            (__Block_byref_ypy_name_0 *)&ypy_name,
            33554432.sizeof(__Block_byref_ypy_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_ypy_name_0 structure ********
struct __Block_byref_ypy_name_0 {
  void *__isa;
__Block_byref_ypy_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 *ypy_name;
};
 
 //********__Block_byref_id_object_copy_131********
 // Copy the block itself (_Block_copy) -- copy the __block bref structure (_Block_object_assign) -- copy the external variables (stored in BREF) from _Block_object_assign to memory
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    // select ypy_name(ypy_name is assigned when bref is initialized)
    _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-74Breakpoint debugging of compilable source code, the order of execution of key methods is:_Block_copy -> _Block_byref_copy -> _Block_object_assign, exactly corresponding to the above three levels of copy

How does block get ypy_name?

  • 1, through the_Block_copyMethods,blockMake a copy toThe heap area
  • 2, through the_Block_object_assignMethod normal copy because__blockThe external variables of the modifier are at the bottomBlock_byref structure
  • 3. Find that external variables still existI have an object, and I take the corresponding object ypy_name from brefCopy toBlock the space, can be used (the same space can be used, different cannot be used). At last,Translation memoryYpy_name is the same memory space as the external ypy_name_Block_object_assignIn the methoddest = object;See)

3.8.3 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

3.8.4 _Block_object_dispose analysis

As with retain and release, _Block_object_object is essentially retain, so there is also a corresponding release, _Block_object_dispose method. Its source code is as follows. Perform different release operations

// 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

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

🌹 feel harvest, can come a wave, like + collection + attention, comment + forward, so as not to find me next time 😁🌹