Resources to prepare
- Libclosure source
Block
Type 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,
Block
Internally 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 external
number
Variable capture - And the heap area
Block
The difference between: assigned to a weak referenceblock
Block
case
Block
Memory 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 stackBlock
The 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:
- will
strongBlock
use__weak
Modify, 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.
Block
Copy 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.
Block
As 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.
systemAPI
containsusingBlock
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:
- when
A
holdB
.B
theretainCount
for+ 1
; - when
A
The triggerdealloc
When will giveB
Put the signal,B
theretainCount
for- 1
. At this time BretainCount
If it is0
, will be calleddealloc
Release 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.
Block
In 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 asBlock
Parameters 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 underlyingcpp
Analysis of the
Block
nature
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.
__block
The 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
int
typea
, corresponding to generation__Block_byref_a_0
Structure.Member variable 2
To the structurea
Take 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:
- from
libobjc.A.dylib
Framework.
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_copy
Delta function, butobjc
The 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.
Block
structure
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_copy
Function, currentBlock
Marked asStackBlock
.
Run directly to the end of the function, read the return value x0 register, and print it:
- The program runs when the current
Block
Conform to theMallocBlock
The conditions, after_Block_copy
The delta function is going to takeBlock
Copy to the heap.
Block
call
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_layout
In the structure, the first address is offset16 bytes
, equivalent to skippingisa
,flags
andreserved
, readinvoke
Function 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 out
signature
,copy
,dispose
Such data.
descriptor
type
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 andBlock
The size of the
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
Copy the code
Block_descriptor_2
: storagecopy
anddispose
Function 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
: storagesignature
The 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 the
Block_descriptor_1
There must be. Straight throughBlock
thedescriptor
Read.
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.
lldb
validationdescriptor
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
:Block
theflags
Identifier.
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 not
0
To prove thatBlock_descriptor_2
There 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 not
0
To prove thatBlock_descriptor_3
There is.
Block
The signature
lldb
Verify 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 translation
24 bytes
.0x0000000102144018
isdescriptor
Structure 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
cpp
Analysis 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_assign
Function.
lldb
Analysis 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
:copy
A 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_assign
Function.
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 return
Block_descriptor_2
Under thecopy
Function.
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_keep
Analysis 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 source
byref_keep
, corresponding tocpp
In 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 heap
Block_byref
, corresponding tocpp
In the__Block_byref_objc_0
Structure.
byref_keep
conclusion
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
Block
Conclusion 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
- through
byref_keep
Function, call_Block_object_assign
Function, passed inobjc
Instance objects
- through
-
The _Block_object_assign function copies the captured external variables to the heap
Block
The 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_copy
Similar, by_Block_call_dispose_helper
Function, call_Block_object_dispose
Function.
_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_copy
Similar to that of thebyref_destroy
Originating 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.