Resources to prepare

  • Libclosure source

BlockType introduction of

There are three types of blocks: GlobalBlock, MallocBlock, and StackBlock.

  • GlobalBlock:

    • Located in global area

    • You cannot capture external variables inside a Block, or use only static or global variables

  • MallocBlock

    • Located in the heap area

    • Use local variables or OC attributes inside blocks and assign values to strongly referenced or copy-modified variables

  • StackBlock

    • Located in the stack area

    • As with mallocblocks, local variables or OC attributes can be used internally. But you cannot assign to strongly referenced or copy-modified variables

GlobalBlock

typedef void(^KCBlock)(id data);
Copy the code

Global Block, which cannot capture external variables, but can use static or global variables:

  • Use static variables, global variables,BlockInternally declared variables, no problem.

MallocBlock

The heap Block, which internally uses local variables or OC attributes and assigns values to strongly referenced or copy-modified variables:

  • The external a variable is captured;

  • Block assigned to a strong reference;

  • The block holds the memory address of the heap block.

StackBlock

Stack blocks are used in much the same way as heap blocks, except that they cannot be assigned to strongly referenced or copy-modified variables:

  • The same goes for the externalnumberVariable capture
  • And the heap areaBlockThe difference between: assigned to a weak referenceblock

Blockcase

BlockMemory copy of

  • NSObject+Block.h

  • NSObject+Block.m

Then implement related cases in the controller:

Code reading:

  • The first step, weakBlock is the stack Block;

  • The second step is to customize the _LGBlock structure according to the underlying Block source code. Blocks can be bridged to custom objects as long as the memory structure is consistent.

  • Third, the Block is essentially a structure, and assigns the first address of the structure to the __strong modified object.

  • In the fourth step, the invoke of the structure is null, that is, the function pointer to the Block.

  • Fifth, assign strongBlock to strongBlock1, which is strongly referenced, and call it.

Reasons for flashback:

  • In the third step, the first address of the structure is assigned to the object, which points to the same memory space.

  • In step 4, the invoke of the structure is null, modifying the same memory space;

  • In step 5, the invoke null Block is assigned to strongBlock1, which is invoked with bad address access and the program flashes back.

Modification case:

First assign the stack Block to the strongly referenced Block and then empty the invoke:

But he still retreated.

Flash back cause: The assignment of the Block is only a shallow copy, which is equivalent to copying the pointer to the object. A new pointer to the object is generated, but the two Pointers still point to the same object

So, the solution is to make a deep copy of the Block before the invoke is empty:

Using LLDB, observe what happens to two blocks after invoke is invoked:

  • A deep copy is equivalent to copying an object to create a new object. Instance variables of each pointer type are copied recursively until the two objects have nothing in common.

Therefore, the stack Block null invoke(pointer) does not affect the heap Block call.

Reference counting handling of external variables

Code interpretation:

  • The first step is to print 1 after objc is initialized without any problems;

  • Second step, print 3 in strongBlock, split into two steps for analysis:

    External objC variable, captured by stack Block, reference count +1;

    StrongBlock assigns a value to strongBlock, copies the stack Block to the heap, makes a deep copy, and the reference count is +1.

  • The third step, assign value to weak-referenced weakBlock, which belongs to stack Block, only capture external OBJC variable, reference count +1.

  • In the fourth step, the stack Block is called copy and assigned to mallocBlock. Only the stack Block is deeply copied with a reference count of +1.

In fact, the third and fourth steps are the same as the decomposition of the second step, so print the results: 1, 3, 4, 5.

The stackBlockThe release of

Code interpretation:

  • First step, weakBlock uses __weak modification and assigns the value to nil;

  • The second step is to define a code Block to implement a heap Block.

  • The third step, the heap Block is assigned to the weakBlock outside the code Block;

  • The fourth step is to call weakBlock after the code block is executed.

Reasons for flashback:

  • In the third step, the heap Block is assigned the value of __weak modified weakBlock, which is equivalent to the mapping relationship;

  • In step 4, when the Block is finished executing, the strongBlock is released as it is a heap Block. WeakBlock, as a mapping, will naturally be set to nil. When it is called, bad address access occurs and the program flashes back.

Modification case:

  • willstrongBlockuse__weakModify, you can print normally.

When the strongBlock is __weak, it becomes the stack Block. Assign it to the __weak modified weakBlock, which is still the stack Block. The life cycle of stack blocks is independent of code blocks and depends on function stack frames, so it can be printed normally.

BlockCopy to the heap

If a Block is a global Block, it is not copied to the heap by any means, even if manually copied, it is still a global Block:

In addition, the system copies blocks to the heap in four ways:

  • Manually Copy;

  • Block as the return value;

  • Strongly referenced or Copy modified;

  • The system API contains usingBlock.

BlockAs a return value

As soon as the variable domain to which a stack Block belongs ends, the Block is destroyed. In an ARC environment, the compiler automatically copies blocks from the stack to the heap. For example, when a Block is returned as a function value, it must be copied to the heap.

systemAPIcontainsusingBlock

When a Block is a function parameter, it needs to be manually copied to the heap. But we don’t need to deal with system apis, such as the usingBlock method in GCD. Any other custom method that passes a Block as a parameter requires manual copy.

A circular reference

Compare these two types of Block code:

  • Block1 has circular references because the block is held by self, and self is used in the block, so self is held by the block. In this case of mutual holding, circular references occur.

  • Block2 doesn’t have circular references, because the owner of the block is UIView, it’s independent of self, it doesn’t hold each other, so it doesn’t have circular references.

The normal process of releasing objects:

  • whenAholdB.BtheretainCountfor+ 1;
  • whenAThe triggerdeallocWhen will giveBPut the signal,BtheretainCountfor- 1. At this time BretainCountIf it is0, will be calleddeallocRelease normally.

Object loop reference process:

  • When A and B hold each other, A’s dealloc cannot trigger, because A waits for B to send A signal before retainCount can proceed -1.

  • However, B’s dealloc cannot be triggered because B is also waiting for A’s signal. At this point, both A and B are waiting for each other to release, resulting in circular references.

Ways to avoid circular references:

  • Weak – strong – dance;

  • Forcibly cutting off the holder in a Block;

  • The holder is passed and used as a Block argument.

weak-strong-dance

__weak typeof(self) weakSelf = self; 

self.block = ^{ 
    __strong typeof(self) strongSelf = weakSelf;     
    NSLog(@"%@",strongSelf.name); 
};
Copy the code
  • Assign self to __weak modified weakSelf. At this time, weakSelf belongs to the mapping of self, pointing to the same memory space, and the reference count of self will not change.

  • WeakSelf is assigned to strongSelf modified by __strong in the Block to avoid the case that self is released early and access to nil.

  • Because strongSelf is a temporary variable, it is automatically released when the Block scope ends, so it is not referenced in a loop.

BlockIn forcibly cut off the holder

__block ViewController *vc = self; 

self.block = ^{ 
    NSLog(@"%@",vc.name);
    vc = nil; 
};

self.block();
Copy the code
  • Use __block to decorate objects, otherwise VC cannot change, that is, it cannot be set to nil;

  • Use end in Block to manually set the object to nil. It is equivalent to manually cutting off the holding relationship to avoid circular reference.

  • Pitfalls: The Block must be called in this way, otherwise the holding cannot be manually severed, and neither self nor Block can be freed, resulting in circular references.

Take the holder asBlockParameters are passed and used

self.vcBlock = ^(ViewController *vc){ 
    NSLog(@"%@",vc.name);
};

self.vcBlock(self);
Copy the code
  • Supply self as an argument to the Block for internal use. When the Block is finished, the VC is automatically released, and the self is also released when the mutual holding relationship is severed.

  • This way, self depends on the end of the Block. If there is delayed code in the Block, the release of self will also be delayed.

The interview questions

In the following cases, will circular references occur?

Case 1:

static ViewController *staticSelf_; 

- (void)blockWeak_static { 
    __weak typeof(self) weakSelf = self; 
    staticSelf_ = weakSelf; 
}

[self blockWeak_static];
Copy the code
  • Circular references occur;

  • Objects that assign self to the __weak modifier are mapped to the same memory space. When weakSelf assigns a global static variable, staticSelf_ will not be released during the program’s run. It will continue to hold self, so self cannot be released.

Case 2:

- (void)block_weak_strong { 
    __weak typeof(self) weakSelf = self; 
    
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf; 
        weakSelf.doStudent = ^{
            NSLog(@"%@", strongSelf); 
        }; 
        
        weakSelf.doStudent();
    }; 
    
    self.doWork(); 
} 

[self block_weak_strong];
Copy the code
  • Circular references occur;

  • Inside doWork, strongSelf holds self. Although strongSelf is a temporary variable, it is held again in doStudent, resulting in a +1 reference count. After doWork completes, the reference count is -1, but the hold in doStudent still exists, so circular references occur.

The underlyingcppAnalysis of the

Blocknature

Create a block.c file with the following code:

#include "stdio.h" 

int main() {
    void(^block)(void) = ^{ 
        printf("LG"); 
    }; 
    
    block(); 
    return 0; 
}
Copy the code

Using the xcrun command, generate the block. CPP file, the underlying C++ file.

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp
Copy the code

Open the block. CPP file and find the main function:

For easy reading, remove strong code

int main() {
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); 
    block->FuncPtr(block); 
    
    return 0; 
}
Copy the code
  • Only two lines of code are generated:

    • Call __main_block_IMPL_0, pass in two arguments, take address and assign block;

    • Call the FuncPtr function of a block, passing in the block.

Find the definition of __main_block_IMPL_0:

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; }}Copy the code

Parameter 1

1static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("LG");
}
Copy the code

Parameter 2

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)};
Copy the code
  • As you can see, the Block is essentially a structure, and the constructor of the structure is called in main.

  • The structure contains two member variables:

    • __block_impL ImpL of structure type;

    • __main_block_DESC_0 structure pointer type Desc.

  • The constructor generates a default value of flags equal to 0, which is assigned to the IMPL flags.

  • The parameter fp is a function pointer to the Block code Block and the FuncPtr of the IMPl is assigned.

  • Parameter desc is assigned to the member variable desc.

  • So in main, the code block->FuncPtr(block) is calling a block.

  • Thus, when a Block is defined only without calling execution, no code Block in the Block is triggered.

Capturing external variables

The code is as follows:

Generate the block. CPP file and find the main function:

int main(){ 

    int a = 8; 
    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 code has changed so that the __main_block_IMPL_0 function has three inputs;

  • The third parameter added is the external variable A.

Find the definition of __main_block_IMPL_0:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

Parameter 1:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a;  // bound by copy
    printf("LG - %d",a);
}
Copy the code

Argument 2:

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)};
Copy the code
  • The member variables in the structure also change. When capturing external variables, the corresponding member variables are generated for storage inside the structure.

  • The member variable a is assigned by the constructor of the structure: a(_a);

  • Bolck is called in main, where the block in FuncPtr comes into play because it captures an external variable:

    • Block is a pointer to its own structure, assigning the member variable A in the block to the temporary variable A, and then printing it;

    • The temporary variables a and __cself->a have the same value but different addresses;

    • Since A is value copy, Bolck code block cannot change the value of A, which will cause compiler code ambiguity. So, a is read-only.

  • Catch an external variable and assign a strong reference variable, which should be a heap Block, but the isa of the impl in the structure is assigned &_NSConcretestackBlock, marked as stack Block. Because you can’t open up memory at compile time, you’ll mark it StackBlock for now. At run time, blocks are copied to the heap as necessary, and mallocblocks are generated.

__blockThe role of

The code is as follows:

#include "stdio.h" 
int main() { 
    __block int a = 18;
    void(^block)(void) = ^{ 
        a++;
        printf("LG - %d",a); 
    }; 
    
    block(); 
    return 0;
}
Copy the code

Generate the block. CPP file and find the main function:

int main() { 
    __Block_byref_a_0 a = { 
        0,
        (__Block_byref_a_0 *)&a, 
        0, 
        sizeof(__Block_byref_a_0), 
        18 
    };
    
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344); 
    
    block->FuncPtr(block);
    
    return 0; 
}
Copy the code
  • inttypea, corresponding to generation__Block_byref_a_0Structure.Member variable 2To the structureaTake the address and convert it to a structure pointer.

Find the __Block_byref_a_0 definition:

struct __Block_byref_a_0 { 
    void *__isa; 
    __Block_byref_a_0 *__forwarding; 
    int __flags;
    int __size; 
    int a; 
};
Copy the code
  • __forwarding stores the address of the a structure;

  • The last member variable, A, stores the value of 18;

  • The structure stores its own address and value.

Find the definition of __main_block_IMPL_0:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

Parameter 1:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 
    __Block_byref_a_0 *a = __cself->a; // bound by ref 
    
    (a->__forwarding->a)++; 
    printf("LG - %d",(a->__forwarding->a));
}
Copy the code

Argument 2:

static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; Dispose function 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*/); }Copy the code
  • The local variable a has the same address as the __cself->a pointer. Once its value is changed, it is equivalent to modifying the value of the external variable.

  • Without __block decoration, it is a value copy, which is a deep copy. Copied values cannot be changed; they point to different memory Spaces;

  • The __block modifier is a shallow copy of the address. The generated objects point to the same memory space, and internal changes are equivalent to changes to external variables.

Assembly analysis

Process analysis

To build the App project, write the following code:

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    
    __block NSObject *objc = [NSObject alloc];
    void (^block)(void) = ^{ 
        NSLog(@"LG_Block %@ ",objc); 
    };
    
    block(); 
}
Copy the code

Set a breakpoint against the block’s definition, run the project, and view the assembly code:

In the project, set the objc_retainBlock symbol breakpoint:

  • fromlibobjc.A.dylibFramework.

Open objC4-818.2 and find the objc_retainBlock function:

id objc_retainBlock(id x) { 
    return (id)_Block_copy(x); 
}
Copy the code
  • call_Block_copyDelta function, butobjcThe implementation is not found in the source code

In your project, set the _Block_copy symbol breakpoint:

From the libsystem_blocks. Dylib framework, which is not open source yet and can be viewed in the libclosure Alternative project.

Blockstructure

According to CPP analysis, Block is a structure in nature. In libclosure-79, go to the _Block_copy function to find the actual Block type: Block_layout.

Find the definition of Block_layout:

  • Isa: a class that identifies Block types;

  • Flags: identifier that represents additional information about a Block in bits, similar to the bit fields in ISA;

  • Reserved: reserved;

  • Invoke: function pointer to the invocation address of the Block implementation;

  • Descriptor: Additional information, such as the number of reserved variables, Block size, and function pointer to copy or dispose.

Find the flags:

enum { BLOCK_DEALLOCATING = (0x0001), // runtime BLOCK_REFCOUNT_MASK = (0xfffe), // runtime BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler #endif BLOCK_IS_NOESCAPE = (1 << 23), // compiler BLOCK_NEEDS_FREE = (1 << 24), // runtime BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code BLOCK_IS_GC = (1 << 27), // runtime BLOCK_IS_GLOBAL = (1 << 28), // compiler BLOCK_USE_STRET = (1 << 29), // compiler: undefined if ! BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), // compiler BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler };Copy the code
  • BLOCK_DEALLOCATING: A release mark, usually used when BLOCK_BYREF_NEEDS_FREE does a bit and mark, passing it to flags to indicate that the Block is free.

  • BLOCK_REFCOUNT_MASK: an optional parameter that stores the reference count.

  • BLOCK_NEEDS_FREE: a flag indicating whether the lower 16 bits are valid, which the program uses to decide whether to increase or decrease the value of the reference count bits;

  • BLOCK_HAS_COPY_DISPOSE: Whether to have a copy helper function for copying to the heap, determining block_description_2;

  • BLOCK_HAS_CTOR: C++ destructor to have or not have blocks;

  • BLOCK_IS_GC: flags whether garbage collection exists, OSX;

  • BLOCK_IS_GLOBAL: whether the flag is a global Block;

  • BLOCK_USE_STRET: In contrast to BLOCK_HAS_SIGNATURE, this Block checks whether the current Block has a signature, which is used for dynamic invocation at Runtime.

  • BLOCK_HAS_SIGNATURE: specifies whether a signature exists.

  • BLOCK_HAS_EXTENDED_LAYOUT: Determine block_description_3 if there is expansion.

The runtimeCopy

Enter the assembly code of the _Block_copy function, read the value of register X0, and print it:

  • Enter the_Block_copyFunction, currentBlockMarked asStackBlock.

Run directly to the end of the function, read the return value x0 register, and print it:

  • The program runs when the currentBlockConform to theMallocBlockThe conditions, after_Block_copyThe delta function is going to takeBlockCopy to the heap.

Blockcall

After _Block_copy completes, go back to viewDidLoad and continue with the Block call:

  • LDR x8, [x0, #0x10] : x0 is the current Block, read x0 + 16 bytes value, assigned to x8;

    • Block_layoutIn the structure, the first address is offset16 bytes, equivalent to skippingisa,flagsandreserved, readinvokeFunction address, assigned tox8;
  • BLR X8: Jump to the invoke function address, equivalent to a call to Block.

descriptor

When _Block_copy completes, the current Block is printed as MallocBlock:

  • It also prints outsignature,copy,disposeSuch data.

descriptortype

Data such as signature, copy, and Dispose are stored in the Descriptor of the Block_layout structure

Descriptor is divided into three types:

  • Block_descriptor_1

  • Block_descriptor_2

  • Block_descriptor_3

Block_descriptor_1 must exist, and Block_descriptor_2 and Block_descriptor_3 are optional

Find the definition of Descriptor:

  • Block_descriptor_1: Stores reserved fields andBlockThe size of the
#define BLOCK_DESCRIPTOR_1 1 
struct Block_descriptor_1 { 
    uintptr_t reserved;
    uintptr_t size; 
};
Copy the code
  • Block_descriptor_2: storagecopyanddisposeFunction pointer to
#define BLOCK_DESCRIPTOR_2 1 
struct Block_descriptor_2 { 
    // requires BLOCK_HAS_COPY_DISPOSE 
    BlockCopyFunction copy;
    BlockDisposeFunction dispose; 
};
Copy the code
  • Block_descriptor_3: storagesignatureThe signature andlayout
#define BLOCK_DESCRIPTOR_3 1 
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE 
    const char *signature; 
    const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Copy the code

To read Block_descriptor_1:

static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock) {
    return aBlock->descriptor; 
}
Copy the code
  • Due to theBlock_descriptor_1There must be. Straight throughBlockthedescriptorRead.

To read Block_descriptor_2:

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;
    desc += sizeof(struct Block_descriptor_1); 
    return (struct Block_descriptor_2 *)desc;
}
Copy the code
  • Check that Block_descriptor_2 does not exist by the bit and operation and return NULL.

  • Otherwise, read the first address of Block_descriptor_1, offset itself, that is, the first address of Block_descriptor_2;

  • Struct pointer strongly converted to Block_descriptor_2 returns.

Read Block_descriptor_3:

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
  • Check that Block_descriptor_3 does not exist by the bit and operation and return NULL.

  • Otherwise, read the first address of Block_descriptor_1, offset itself, that is, the first address of Block_descriptor_2;

  • Check whether Block_descriptor_2 exists by bit and operation:

    • If yes, offset Block_descriptor_2, that is, the first address of Block_descriptor_3.

    • Does not exist, forcibly converted to Block_descriptor_3 is returned.

Because Block_descriptor_2 and Block_descriptor_3 have the same memory structure, the address offset Block_descriptor_1 can be strongly converted to 2 or 3.

lldbvalidationdescriptor

Flags bit and operation to tell whether Block_descriptor_2 and Block_descriptor_3 exist.

Using the x/8g command, print the Block’s memory structure:

(lldb) x/8g 0x283aaa430 
0x283aaa430: 0x00000001de1c0880 0x00000000c3000002
0x283aaa440: 0x00000001021422ac 0x0000000102144018 
0x283aaa450: 0x0000000283aaa400 0x0000000000000000 
0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
Copy the code
  • 0xc3000002:BlocktheflagsIdentifier.

Verify Block_descriptor_2:

//BLOCK_HAS_COPY_DISPOSE = (1 << 25) 

(lldb) p/x 1 << 25 
(int) $7 = 0x02000000 

(lldb) p/x (0xc3000002 & 0x02000000) 
(unsigned int) $8 = 0x02000000
Copy the code
  • The result of the operation is not0To prove thatBlock_descriptor_2There is.

Verify Block_descriptor_3:

//BLOCK_HAS_SIGNATURE = (1 << 30)

(lldb) p/x 1 << 30 
(int) $10 = 0x40000000 

(lldb) p/x (0xc3000002 & 0x40000000) 
(unsigned int) $16 = 0x40000000
Copy the code
  • The result of the operation is not0To prove thatBlock_descriptor_3There is.

BlockThe signature

lldbVerify the signature

Using the x/8g command, print the Block’s memory structure:

(lldb) x/8g 0x283aaa430
0x283aaa430: 0x00000001de1c0880 0x00000000c3000002 
0x283aaa440: 0x00000001021422ac 0x0000000102144018 
0x283aaa450: 0x0000000283aaa400 0x0000000000000000 
0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
Copy the code
  • First address translation24 bytes.0x0000000102144018isdescriptorStructure pointer to.

Using the x/8g command, the memory structure of the output descriptor is:

(lldb) x/8g 0x0000000102144018 
0x102144018: 0x0000000000000000 0x0000000000000028
0x102144028: 0x00000001021422e0 0x00000001021422f0 
0x102144038: 0x00000001021433e3 0x0000000000000010 
0x102144048: 0x00000001de78a280 0x00000000000007c8
Copy the code
  • 0x00000001021422e0: copy function pointer;

  • 0x00000001021422F0: Dispose function pointer;

  • 0x00000001021433e3: Signature Signature.

Strong output to 0x00000001021433E3:

(lldb) po (char *)0x00000001021433e3 "v8@? 0"Copy the code

Signature meaning

For signatures. 0 explanation:

  • V: Return value type viod

  • 8: Memory occupied by the method 8 bytes

  • @? : Type of parameter 0

  • 0: the starting position of parameter 0, starting with 0 bytes

For signature details, you can use NSMethodSignature’s signatureWithObjCTypes method to output:

(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] <NSMethodSignature: 0xbb2001894a750adf> number of arguments = 1 frame size = 224 is special struct return? NO 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: -------- -------- -------- -------- type encoding (@) '@? ' flags {isObject, isBlock} modifiers {} frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8}Copy the code
  • Number of arguments = 1;

  • is special struct return? NO: NO return value.

  • Return value: Return value:

    • Type encoding (v) ‘v’ : short for void;

    • Memory {offset = 0, size = 0} : No value is returned, so size is 0.

  • Argument 0: argument 0;

  • type encoding (@) ‘@? ‘: where @ is the id type,? Unknown type;

  • Flags {isObject, isBlock} : both Object and Block;

  • Memory {offset = 0, size = 8} : Parameter 0 starts with 0 bytes and occupies 8 bytes.

Source code analysis

Source code analysis using alternative engineering Libclosure:

When a Block captures an object decorated with __block, the underlying layer triggers a three-layer copy of the Block.

_Block_copy

Enter the _Block_copy function:

void *_Block_copy(const void *arg) { struct Block_layout *aBlock; If arg is NULL, return NULL if (! arg) return NULL; // The following would be better done as a switch statement // The Block_layout type aBlock = (struct Block_layout *)arg; // if (aBlock->flags & BLOCK_NEEDS_FREE) {// latches on high = 1 latching_inCR_int (&ablock ->flags);  return aBlock; Else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock; // If (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock; } else {// stack - stack (compile time) // Its a stack block. Make a copy. Size_t size = Block_size(aBlock); size_t size = Block_size(aBlock); struct Block_layout *result = (struct Block_layout *)malloc(size); // Failed to open, return NULL if (! result) return NULL; Memmove (result, aBlock, size); // bitcopy first #if __has_feature(ptrauth_calls) // Resign the invoke pointer as it uses address authentication. result->invoke = aBlock->invoke; #if __has_feature(ptrauth_signed_block_descriptors) if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) { uintptr_t oldDesc = ptrauth_blend_discriminator( &aBlock->descriptor, _Block_descriptor_ptrauth_discriminator); uintptr_t newDesc = ptrauth_blend_discriminator( &result->descriptor, _Block_descriptor_ptrauth_discriminator); result->descriptor = ptrauth_auth_and_resign(aBlock->descriptor, ptrauth_key_asda, oldDesc, ptrauth_key_asda, newDesc); } #endif #endif #endif #endif #endif #endif #endif #endif #endif # ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed // And reference counting result is initialized to 1 - > flags | = BLOCK_NEEDS_FREE | 2; _Block_call_copy_helper(result, aBlock); // Set isa last so memory analysis tools see a fully initialized object. // ISA points to _NSConcreteMallocBlock result->isa = _NSConcreteMallocBlock; return result; }}Copy the code
  • The _Block_copy function is responsible for copying Block objects from the stack to the heap.

  • The arg argument is the Block_layout object;

  • If it was already on the heap, the reference count is +1;

  • If the Block is in the global area, the Block itself is returned without reference counting or copying.

  • If it was on the stack, it is copied to the heap, the reference count is initialized to 1, and the _Block_call_copy_helper method (if it exists) is called;

  • The return value is the address of the copied Block.

_Block_object_assign

cppAnalysis of the

Open the CPP file and find the definition of the __main_block_desc_0_DATA structure, the second argument to declare blocks:

//void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344); 

__main_block_desc_0_DATA = { 
    0, 
    sizeof(struct __main_block_impl_0), 
    __main_block_copy_0, 
    __main_block_dispose_0 
};
Copy the code
  • __main_block_copy_0: copy in Block_descriptor_2;

  • __main_block_dispose_0: Dispose in Block_descriptor_2.

Find the definition of __main_block_copy_0:

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*/); 
}
Copy the code
  • call_Block_object_assignFunction.

lldbAnalysis of the

Using the x/8g command, print the Block’s memory structure:

(lldb) x/8g 0x000000028167eca0 
0x28167eca0: 0x00000001de1c0880 0x00000000c3000002 
0x28167ecb0: 0x0000000104f1a2ac 0x0000000104f1c018 
0x28167ecc0: 0x000000028167ec70 0x0000000000000000
0x28167ecd0: 0x000021a1de1c6221 0x0000000000000000
Copy the code

Using the x/8g command, the memory structure of the output descriptor is:

(lldb) x/8g 0x0000000104f1c018
0x104f1c018: 0x0000000000000000 0x0000000000000028 
0x104f1c028: 0x0000000104f1a2e0 0x0000000104f1a2f0 
0x104f1c038: 0x0000000104f1b3e3 0x0000000000000010 
0x104f1c048: 0x00000001de78a280 0x00000000000007c8
Copy the code
  • 0x0000000104f1a2e0:copyA function pointer

Dis-s to read assembly code:

(LLDB) dis-s 0x0000000104F1a2e0 004 -block structure and signature '__copy_helper_block_E8_32r: 0x104F1a2e0 <+0>: add x0, x0, #0x20; =0x20 0x104f1a2e4 <+4>: ldr x1, [x1, #0x20] 0x104f1a2e8 <+8>: mov w2, #0x8 0x104f1a2ec <+12>: b 0x104f1a484 ; symbol stub for: _Block_object_assignCopy the code
  • call_Block_object_assignFunction.

Call time

In the _Block_copy function, call the _Block_call_copy_helper function

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock) { 
    if (auto *pFn = _Block_get_copy_function(aBlock)) pFn(result, aBlock); 
}
Copy the code

Enter the _Block_get_copy_function function

static inline __typeof__(void (*)(void *, const void *)) _Block_get_copy_function(struct Block_layout *aBlock) { if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL; void *desc = _Block_get_descriptor(aBlock); #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) { struct Block_descriptor_small *bds = (struct Block_descriptor_small *)desc; return _Block_get_relative_function_pointer( bds->copy, void (*)(void *, const void *)); } #endif struct Block_descriptor_2 *bd2 = (struct Block_descriptor_2 *)((unsigned char *)desc + sizeof(struct Block_descriptor_1)); return _Block_get_copy_fn(bd2); }Copy the code
  • If copy and dispose are present, get the Block_descriptor_2 structure pointer from memory.

  • Call the _Block_get_copy_fn function, passing in Bd2.

Enter the _Block_get_copy_fn function:

static inline __typeof__(void (*)(void *, const void *)) _Block_get_copy_fn(struct Block_descriptor_2 *desc) {
    return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy); 
}
Copy the code
  • Processed returnBlock_descriptor_2Under thecopyFunction.

Finally, go back to the _Block_call_copy_helper function, assign the address of copy function to pFn, and call it through result, aBlock (pFn).

Source code analysis

In the source, find out what kind of external variables a Block captures

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_BLOCK = 7, // a block variable 
    BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers 
    BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines. 
};
Copy the code
  • BLOCK_FIELD_IS_OBJECT: common object type;

  • BLOCK_FIELD_IS_BLOCK: Block type as variable;

  • BLOCK_FIELD_IS_BYREF: variable decorated with __block;

  • BLOCK_FIELD_IS_WEAK: weak reference variable;

  • BLOCK_BYREF_CALLER: return call object, processing block_byref internal object memory will add an additional flag, used in conjunction with flags.

Enter the _Block_object_assign function:

DestAddr = destAddr; // Copy helper (); // destAddr = destAddr; 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_default = fn (arc) / / by default, do nothing But in _Block_use_RR(), the retain function is set by Objc Runtime or CoreFoundation, which may be associated with runtime, _Block_retain_object(object); * *dest = object; break; case BLOCK_FIELD_IS_BLOCK: /******* void (^object)(void) = ... ; [^{ object; } copy]; ********/ / copy dest to the heap object *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]; ********/ // make dest point to byref *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]; ********/ // make dest point to object *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]; ********/ // make dest point to object *dest = object; break; default: break; }}Copy the code
  • Common object type, handed to the system ARC processing, make dest point to the target pointer to object;

  • Block type as a variable, call _Block_copy function, copy dest point to the heap object;

  • Using the __block modified variable, call _Block_byref_copy so that dest points to the byref copied to the heap.

_Block_byref_copy

Enter the _Block_byref_copy function:

// 1. If byref is on the heap, copy it to the heap. Copy of including Block_byref, Block_byref_2, Block_byref_3 / / be __weak modified byref will be modified isa for _NSConcreteWeakBlockVariable / / byref originally Forwarding also refers to byref on the heap; // 2. If byref is already on the heap, only one reference count is added. Static struct Block_byref *_Block_byref_copy(const void *arg) {// arg is struct Block_byref * SRC = (struct Block_byref *)arg; If ((SRC ->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {SRC points to stack // Allocate memory in the heap for the new BYref 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. // Why 2? Non-gc one for the caller, one for the stack // One for the caller. SRC ->forwarding = copy SRC forwarding also points to the copy, equivalent to refer to the copy copy - > flags = SRC - > flags | BLOCK_BYREF_NEEDS_FREE | 4. Copy ->forwarding = copy; // Patch heap copy to point to itself // Byref forwarding now refers to byref SRC ->forwarding = copy; // patch stack to point to heap copy // copy size copy->size = SRC ->size; SRC has copy/dispose helper if (SRC ->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {// Trust copy helper to copy everything Of interest // If more than one field shows up in a byref block this is wrong XXX struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1); struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1); Dispose helper = src2->byref_keep; dispose helper = src2->byref_keep; copy2->byref_destroy = src2->byref_destroy; // If SRC has an extended layout, 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); // The layout string is not copied to the heap because it is const and not on the stack. Copy3 ->layout = src3->layout; // copy helper (*src2->byref_keep)(*src2->byref_keep)(copy, SRC); } else { // Bitwise copy. // This copy includes Block_byref_3, // SRC does not copy/dispose helper // Copy all data after Block_byref to the copy, Block_byref_3 memmove(copy+1, SRC +1, SRC -> size-sizeof (* SRC)); } // Already copied to the heap // SRC is copied on the 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
  • Copy Block_byref, which belongs to the second layer of the three-layer copy.

  • If the reference count is 0, allocate memory in the heap for the new BYREF:

    • Redirect the forwarding of byref on the heap to itself;

    • Forwarding of byref on stack also points to byref on heap;

    • Byref_keep initiates the third layer copy of the Block.

  • If already on the heap, only the reference count is +1;

byref_keepAnalysis of the

Find the definition of byref_keep:

struct Block_byref {
    void *isa; 
    struct Block_byref *forwarding; 
    volatile int32_t flags; // contains ref count
    uint32_t size; 
};

struct Block_byref_2 { 
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE 
    
    BlockByrefKeepFunction byref_keep; 
    BlockByrefDestroyFunction byref_destroy; 
};

struct Block_byref_3 { 
    // requires BLOCK_BYREF_LAYOUT_EXTENDED 
    const char *layout;
};
Copy the code

View the corresponding CPP file:

struct __Block_byref_objc_0 { 
    void *__isa; 
    __Block_byref_objc_0 *__forwarding; 
    int __flags; 
    int __size; 
    void (*__Block_byref_id_object_copy)(void*, void*); 
    void (*__Block_byref_id_object_dispose)(void*); 
    NSObject *objc; 
};
Copy the code
  • In the sourcebyref_keep, corresponding tocppIn the__Block_byref_id_object_copy.

Find the __Block_byref_id_object_copy assignment:

__Block_byref_objc_0 objc = {
    (void*)0, 
    (__Block_byref_objc_0 *)&objc, 
    33554432, 
    sizeof(__Block_byref_objc_0), 
    __Block_byref_id_object_copy_131, 
    __Block_byref_id_object_dispose_131,
    ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"))
};
Copy the code
  • The incoming__Block_byref_id_object_copy_131.

Find the definition of __Block_byref_id_object_copy_131:

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

Check the parameters passed in for the source code:

(*src2->byref_keep)(copy, src);
Copy the code
  • Passed into the heapBlock_byref, corresponding tocppIn the__Block_byref_objc_0Structure.

byref_keepconclusion

Byref_keep is called, and the underlying _Block_object_assign function is called again

(char*) DST + 40, the structure is stored in translation, is passed objC instance object

Enter the _Block_object_assign function and match the copy logic of the common object type

Copy the object to the heap to complete the third layer copy of the Block

BlockConclusion of three copies

When a Block captures an object decorated with __block, the underlying layer triggers a three-layer copy of the Block

  • The _Block_copy function is responsible for copying Block objects from the stack to the heap

    • Call the _Block_object_assign function from _Block_copy→_Block_call_copy_helper

    • Pass in the Block_byref structure pointer of type BLOCK_FIELD_IS_BYREF and call the _Block_byref_copy function

  • The _Block_byref_copy function copies Block_byref to the heap

    • throughbyref_keepFunction, call_Block_object_assignFunction, passed inobjcInstance objects
  • The _Block_object_assign function copies the captured external variables to the heap

BlockThe release of

If the Block is on the heap, you need to release it. We don’t need releases for blocks in the global area or in the stack area.

 _Block_release

Enter the _Block_release function:

Release is required for blocks on the heap. Release is not required for both global and stack blocks. Void _Block_release(const void *arg) {struct Block_layout *aBlock = (struct Block_layout *)arg; // Block == nil if (! aBlock) return; If (aBlock->flags & BLOCK_IS_GLOBAL) return; // If (aBlock->flags & BLOCK_IS_GLOBAL) return; // Block is not on the heap. (aBlock->flags & BLOCK_NEEDS_FREE)) return; // The reference count is reduced by 1. If the reference count is reduced to 0, it returns true. If (latching_decr_int_should_deallocate(&aBlock->flags)) {dispose Helper, Dispose Helper method does operations such as destroying BYref _Block_call_dispose_helper(aBlock); // _Block_destructInstance does nothing, the function body is empty _Block_destructInstance(aBlock); free(aBlock); }}Copy the code
  • and_Block_copySimilar, by_Block_call_dispose_helperFunction, call_Block_object_disposeFunction.

_Block_object_dispose

Enter the _Block_object_dispose function:

// Block and BYref dispose object Their dispose Helper calls the void _Block_object_dispose(const void *object, Const int flags) {switch (os_assumes (flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {/ / if it is byref case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: case BLOCK_FIELD_IS_BYREF: // Get rid of the __block data structure held in a Block _Block_byref_release(object) break; Case BLOCK_FIELD_IS_BLOCK: // Release the block _Block_release(object); break; case BLOCK_FIELD_IS_OBJECT: _Block_use_RR() may be set to release by Objc Runtime or CoreFoundation, _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
  • Common object type, to the system ARC processing;

  • Block type as variable, call _Block_release;

  • The byref object is released by calling _Block_byref_release using __block modified variables.

_Block_byref_release

Enter the _Block_byref_release function:

// Byref on the heap requires release, but not on the stack. // Release is the reference count minus 1. If the reference count goes to 0, Static void _Block_byref_release(const void *arg) {struct Block_byref *byref = (struct Block_byref *)arg;  // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?) Byref = byref->forwarding; byref = byref->forwarding; // byref is copied to the heap, Release if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;  os_assert(refcount); // The reference count is reduced by 1. If the reference count is reduced to 0, it returns true. If (latching_decr_int_should_deallocate(&byref->flags)) {if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1); // Dispose Helper is inside Block_byref_2 (*byref2->byref_destroy)(byref); } free(byref); }}}Copy the code
  • and_Block_byref_copySimilar to that of thebyref_destroyOriginating objectrelease.

Corresponding CPP code:

static void __Block_byref_id_object_dispose_131(void *src) { 
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
Copy the code
  • Call the _Block_object_dispose function, passing in an objC instance object.

  • Enter the _Block_object_dispose function and hit the release logic of the normal object type.