preface
In IOS development, we use a lot of blocks. In general, we only stay at the level that can be used, and we don’t know how to implement the bottom layer of the specific block. Maybe a lot of people will just ask if they know how to use blocks and know what the underlying principles are. For example, how a block captures variables and what type is underneath the block. If you understand the underlying principles, you don’t have these questions. As a developer, you need to know not only why but why
The preparatory work
- Objc – 818.2 –
- libclosure-79
block
type
There are three main types of blocks: global blocks, heap blocks, and stack blocks
globalblock
The case code is as follows
static int b = 100;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
void (^block)(void) = ^ {NSLog(@"block----%d",b);
};
void (^block1)(void) = ^ {NSLog(@"block1");
};
NSLog(@"block-----%@",block);
NSLog(@"block1----%@",block1);
}
@end
Copy the code
2021- 08- 29 14:38:10.593203+0800Block principle [4061:834603] block-----<__NSGlobalBlock__: 0x104144090>
2021- 08- 29 14:38:10.593286+0800Block principle [4061:834603] block1----<__NSGlobalBlock__: 0x1041440b0>
Copy the code
__NSGlobalBlock__ means that the global block is in the global area and no external variables are used inside the block, or only static or global variables are used. A block in this case is a global block
The heap areablock
The case code is as follows
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
int a = 10;
void (^block)(void) = ^ {NSLog(@"block----%d",a);
};
NSLog(@"block----%@",block);
}
@end
Copy the code
2021- 08- 29 14:45:41.421585+0800Block principle [4068:835893] block----<__NSMallocBlock__: 0x2831dc360>
Copy the code
__NSMallocBlock__ is the heap block, which is obviously located in the heap. Use external variables or OC attributes inside blocks and assign values to strongly referenced or copy-modified variables
The stack areablock
The case code is as follows
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
int a = 10;
void (^__weak block)(void) = ^ {NSLog(@"block----%d",a);
};
NSLog(@"block----%@",block);
}
@end
Copy the code
2021- 08- 29 15:39:03.308085+0800Block principle [4148:850196] block----<__NSStackBlock__: 0x16dc85bf8>
Copy the code
__NSStackBlock__ is a stack block that uses external variables or OC attributes inside the block, but cannot be assigned to strongly referenced or copy-modified variables
block
Analysis of the underlying
Block underlying analysis through the clang generated. CPP file to view the underlying compiled source code
block
Catch ordinary variables (not__block
Modified)
block
Capture the object
Create an NSObject object to use inside the block. The following code
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSObject * obj = [NSObject alloc];
void (^ block)(void) = ^ {NSLog(@"----%@",obj);
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Use clang to compile main.m file into main. CPP file. Remove some unimportant code from main. CPP file for easy viewing
Block is a structure at the bottom, and __block_impl and __main_block_DESc_0 are nested in the __main_block_IMPL_0 structure
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
Copy the code
FuncPtr in the __block_impl structure is used to hold the address of the task function, the __main_block_func_0 function
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};
Copy the code
The variables copy and dispose in the __main_block_DESc_0 structure are very important functions that hold the __main_block_copy_0 and __main_block_dispose_0 function addresses. Called when a block is copied and released
block
Capture non-object types
Use variables of type int for example exploration. The following code
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int a = 10;
void (^ block)(void) = ^ {NSLog(@"----%d",a);
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Use clang to compile main.m file into main. CPP file. Remove some unimportant code from main. CPP file for easy viewing
The __main_block_DESc_0 structure has no copy and dispose variables, which means that there are no copy and dispose functions. This is important and affects the three-layer copy of the block
Summary: The main function has two steps related to block operations
- right
block
Is initialized and assigned to the structure of__main_block_func_0
Task function function,&__main_block_desc_0_DATA
The address of the function that describes the information and the external variablesobj
block
The underlying layer is compiled to a structure typeblock
The structure now automatically generates oneNSObject *__strong obj
When assigning to the structureobj(_obj)
throughC++
Way toblock
Structure of theobj
The assignment- will
__main_block_func_0
Assigned toblock
Variables in a structure__block_impl
In theFuncPtr
save - will
&__main_block_desc_0_DATA
Assigned toblock
Variables in a structure__main_block_desc_0
In theDesc
save
- call
blcok
In the structure__block_impl
Variables in theFuncPtr
Variables perform task function functionsFuncPtr
When executed, theblcok
As a parameter, because we want to getblcok
In theobjc
variable__main_block_func_0
The variables used in the method are actually retrievedblcok
Newly generated in the structureobj
. In structureobj
And external variablesobj
It points to the same memory space
blcok
Both assignment and execution are passedblcok
Internal data or functions to execute, are usedblcok
Data stored in
Question: Why can’t normal variables (not decorated with __block) be modified inside BLCOK
Because the blCOK variable obj points to the same block of memory as the external variable obj, if the blCOK variable obj points to the same block of memory as the external variable obj, if the blCOK variable obj points to the same block of memory as the external variable obj, if the blCOK variable obj points to the same block of memory as the external variable obj. At this point, the compiler doesn’t know which obJ to use and the code is ambiguous, so it can’t be modified and is only readable. This is what people call value copying
block
capture__block
Modified variable
block
capture__block
Modified object type
Add the __block modifier to the obj variable and modify it in block memory. The following code
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
__block NSObject * obj = [NSObject alloc];
void (^ block)(void) = ^{
obj = [NSObject alloc];
NSLog(@"----%@",obj);
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Use clang to compile main.m file into main. CPP file. Remove some unimportant code from main. CPP file for easy viewing
In main. CPP, you can see that blCOK captures and invokes the same process as captures non-__block decorated variables, except that the data structure type of the captured variables has changed
The compiler compiles the underlying __block modified variable into a structure __Block_byref_obj_0
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;// the __Block_byref_obj_0 address is assigned to itself by default
int __flags;/ / logo
int __size;/ / size
void (*__Block_byref_id_object_copy)(void*, void*);/ / copy method
void (*__Block_byref_id_object_dispose)(void*);/ / the dispose method
NSObject *__strong obj;// Address of the object
};
Copy the code
When the compiler compiles the underlying __block modified variable into a structure, it makes an initial assignment
(__Block_byref_obj_0 *)&obj
Assigned to__forwarding
In fact, it is__forwarding
Point to the__Block_byref_obj_0
Address. In simple terms__Block_byref_obj_0
In the__forwarding
Point to the__Block_byref_obj_0
__Block_byref_id_object_copy_131
Assigned to__Block_byref_id_object_copy
__Block_byref_id_object_dispose_131
Assigned to__Block_byref_id_object_dispose
- Assigns the address of the object to the variable
NSObject *__strong obj
The struct of blCOk initialassignment takes the address of the variable’s __Block_byref_obj_0 obj structure as an argument
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
__Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
_obj->__forwarding assigns the variable __Block_byref_obj_0 *obj to the __main_block_IMPL_0 structure, which means that obj also points to the __Block_byref_obj_0 structure
(block)->FuncPtr(block) call task function function code is as follows
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_obj_0 *obj = __cself->obj; // bound by ref
(obj->__forwarding->obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)
((id)objc_getClass("NSObject"), sel_registerName("alloc"));
NSLog((NSString *)&__NSConstantStringImpl,(obj->__forwarding->obj));
}
Copy the code
__Block_byref_obj_0 *obj
=__cself- >obj
theblcok
In structureobj
Assigned toobj
, that is, the__Block_byref_obj_0
The address assigned to__Block_byref_obj_0 *obj
obj->__forwarding
Points to the__Block_byref_obj_0
Address of the structureobj->__forwarding->obj
is__Block_byref_obj_0
In structureNSObject *__strong obj
variableobj->__forwarding->obj
Modify theobj
When,block
The orientation of the internal and external variables is not changed, but the orientation of the internal and external variables is changedobj
Actually accessedobj
It’s the same thing, regardless of thatobj
Has it been modified?
The above explored captured __block modified object types, and the following explored captured non-object types
block
capture__block
Modified object type
An example of a variable of type int decorated with __block. The following code
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
__block int a = 10;
void (^ block)(void) = ^ {NSLog(@"----%d",a);
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Use clang to compile main.m file into main. CPP file. Remove some unimportant code from main. CPP file for easy viewing
Block captures the __block-modified object type. There is no copy or dispose in the __Block_byref_obj_0 structure. This is important and affects the three-layer copy of the block
Conclusion:
__block
The modified variable generates an underlying one__Block_byref_obj_0
The structure of the body__Block_byref_obj_0
The structure holds the address of the object as well as__Block_byref_obj_0
addressblcok
Both internal and external variables are pointing__Block_byref_obj_0
Address. while__Block_byref_obj_0
The variables in theobj
When it changesblcok
Internal and external variables still point to__Block_byref_obj_0
Address, and then getobj
__blcok
Modify the variable indicator diagram
According to the.cpp
A graph drawn by the assignment and modification process of variables in a file. Just to make sense of it__blcok
Why can decorated variables be modified
block
No variables captured (global or static)
Blocks capture uncaptured variables in the same way they capture global and static variables
block
Uncaptured variable
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
void (^ block)(void) = ^{
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Use clang to compile main.m file into main. CPP file. Remove some unimportant code from main. CPP file for easy viewing
If a block does not capture a variable, no corresponding variable is automatically generated within the block, and the __main_block_desc_0 structure has no copy and disponse functions
block
Capture global or static variables
static int a = 100;
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
void (^ block)(void) = ^ {NSLog(@"----%d",a);
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Use clang to compile main.m file into main. CPP file. Remove some unimportant code from main. CPP file for easy viewing
A block captures global and static variables in the same way as it does uncaptured variables, only global or static variables are used
block
The underlying inquiry
Through the analysis of main. CPP file, the blCOK variable assignment and block call are roughly clarified. However, the process of stack BLCOK becoming heap BLCOK is not understood. The following will be explored by assembling the tracking process
globalblock
The underlying exploration of
Set breakpoints for BLCOK, as shown below
The block has no values, but the variables in the block are similar to the block structure in the underlying main.cpp file. Assembly debugging is carried out below
Assembly shows a jump to objc_retainBlock, directly to the objc_retainBlock symbol breakpoint. Continue to debug
objc_retainBlock
The next step in assembly jumps to_Block_copy
objc_retainBlock
Method is inlibobjc.A.dylib
The source code in the library- In a real situation
x0
The first parameter of the method is the receiver of the message. throughlldb
Debugging finds that the message receiver at this point isblock
. And at this pointblock
It’s a globalblock
Add a symbolic breakpoint to _Block_copy and continue debugging
_Block_copy
Methods in thelibsystem_blocks.dylib
The source code in the library- through
lldb
Debugging found_Block_copy
The message receiver isblock
. At this timeblock
It’s still a global pictureblock
_Block_copy The end of the assembly will return a value, _Block_copy intermediate assembly is more difficult to read. But whatever the _Block_copy intermediate procedure did, all you need now is its return value. In the real case, the return value of the method is stored in the X0 register
Global block after _Block_copy does nothing, returns global block directly
The heap areablock
The underlying exploration of
Set breakpoints for BLCOK, as shown below
The block in the diagram is called blCOk, the heap area, which also calls the objc_retainBlock method
The block shown in the figure is not a heap block, but a stack block. With this question continue to go down
After _Block_copy, a block in the stack becomes a block in the heap. If the address of the block in the stack and the block in the heap is different, it indicates that the memory is newly created in the heap. However, the invoke, cPOy, and disponse variables in the stack block are the same, so it is a bold guess that the stack block will run cPOY to the stack block, form a new heap block and return. The specific process to explore _Block_copy source code
The stack areablock
The underlying exploration of
To stack areablcok
Set the breakpoint, as shown below
The diagram shows that the stack block does not call the objc_retainBlock method and therefore does not call the _Block_copy method
conclusion
- global
block
Called at run time_Block_copy
Method is still globalblock
- The heap area
block
The stack area is determined by compile timeblock
Called at run time_Block_copy
Method to generate a new heap areablock
- The stack area
blcok
Not to_Block_copy
The operation of the
Conclusion: _Block_copy is performed if a block is assigned to a strongly referenced or copy-modified variable, and _Block_copy is not performed if a variable is assigned to an __weak modifier
block
The type ofBlock_layout
The structure of the body
The.cpp files and assembler give you an idea of the types of blocks, but it’s not very clear. Different types of BLCOK variables are also different, only through the underlying source code to explore the block type. The objc_retainBlock method is a global search for objc_retainBlock in the objC4-818.2 source code from the libobjC.a. dylib source library
The objc_retainBlock method calls the _Block_copy method in line with the assembly flow. No implementation of _Block_copy is found in the global search for _Block_copy. It is known in the assembly flow that the _Block_copy method is in the libsystem_blocks. Dylib source library, but this source library is not open source. The _Block_copy method was found in the libclosure-79 source library after much exploration by the developers
The underlying structure of BLCOK found in the _Block_copy method is a Block_layout structure
struct Block_layout {
/ / the type of block
void * __ptrauth_objc_isa_pointer isa; / / 8 bytes
// The information used to identify 'blcok' is stored bitwise, similar to the 'bits' of isa in the object
volatile int32_t flags;/ / 4 bytes
// Reserve the field
int32_t reserved;/ / 4 bytes
// Function pointer, save the implementation address of the task function is FuncPtr in the. CPP file
BlockInvokeFunction invoke; / / 8 bytes
// Description
struct Block_descriptor_1 *descriptor;/ / 8 bytes
};
Copy the code
Block_layout specifies the meaning of the structure variable
isa
Said:block
Type (stack, heap, global)flags
: identifiers are similar to those in objectsisa
thebits
reserved
: Reserved fieldinvoke
: function pointer, which holds the implementation address of the task function.cpp
In the fileFuncPtr
functiondescriptor
: Description
Flag logo
// Values for Block_layout->flags to describe block objects
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
};
Copy the code
- The first
1
positionBLOCK_DEALLOCATING
: Release flag, – commonBLOCK_NEEDS_FREE
Do bits and operations, passed in togetherFlags
To inform theblock
Can release - low
16
positionBLOCK_REFCOUNT_MASK
: Stores the reference count value, which is an optional parameter - The first
24
positionBLOCK_NEEDS_FREE
Low:16
A valid flag by which the program decides whether to increase or decrease the value of the reference count bit - The first
25
positionBLOCK_HAS_COPY_DISPOSE
: Whether the copy helper function is available (a copy helper function
) - The first
26
positionBLOCK_HAS_CTOR
: Owned or notblock
The destructor - The first
27
positionBLOCK_IS_GC
: Indicates whether there is garbage collection; //OS X
- The first
28
positionBLOCK_IS_GLOBAL
: Indicates whether the flag is globalblock
- The first
30
positionBLOCK_HAS_SIGNATURE
And:BLOCK_USE_STRET
In contrast, judge the presentblock
Whether you have a signature. Used forruntime
Time dynamic call
Among these tag bits, BLOCK_HAS_COPY_DISPOSE and BLOCK_HAS_SIGNATURE are particularly important
BLOCK_HAS_COPY_DISPOSE
Indicates whether there areBlock_descriptor_2
BLOCK_HAS_SIGNATURE
Indicates whether there areBLOCK_DESCRIPTOR_3
- Why not
Block_descriptor_1
Flag bit, becauseBlock_descriptor_1
It’s a must - It can be determined by the marker bit
Block_descriptor_1
It has to be,Block_descriptor_2
andBLOCK_DESCRIPTOR_3
Is optional
Descriptors are divided into three main types: Block_descriptor_1, Block_descriptor_2, and BLOCK_DESCRIPTOR_3
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;/ / 8 bytes
uintptr_t size;/ / 8 bytes
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;/ / 8 bytes
BlockDisposeFunction dispose;/ / 8 bytes
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;/ / 8 bytes
const char *layout; //8 bytes // contents Depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Copy the code
Block_descriptor_1
Is the structure type, wherereserved
Indicates reserved information,size
saidblock
The size of theBlock_descriptor_2
Is the structure type, wherecopy
Deposit iscopy
Function address,dispose
Deposit isdispose
Function addressesBlock_descriptor_3
Is the structure type, wheresignature
Indicates signature information.layout
saidblock
Extended layout of
Now let’s look at the constructor of the next descriptor, how does it get descriptor
struct Block_descriptor_1 *desc1 = layout->descriptor;
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1);// Address offset to get Descriptor_2
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);/ / descriptor_1 address
desc += sizeof(struct Block_descriptor_1);// Address offset descriptor_1 size
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {// If Descriptor_2 exists
desc += sizeof(struct Block_descriptor_2);// Continue address offset Descriptor_2 size
}
return (struct Block_descriptor_3 *)desc;// Get Descriptor_3
}
Copy the code
Block_descriptor_1 is assigned directly, whereas Block_descriptor_2 and Block_descriptor_3 are obtained by address offset. Verify again that Block_descriptor_1 is required by default in conjunction with the above flag bit, and that Block_descriptor_2 and Block_descriptor_3 are optional, depending on the flag bit
lldb
validationBlock_layout
In the variable
- The heap area
block
Debug verification
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSObject * obj = [NSObject alloc];
void (^ block)(void) = ^ {NSLog(@"----%@",obj);
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Debug at the beginning of the _Block_copy assembly or at the RET location
flags
The value is0x00000000c3000002
.descriptor
The value is0x0000000100390078
.- print
descriptor
The information stored in thesize
The value of the0x0000000000000028
The conversion10
Base is equal to the40
isBlock_layout
The size of the structure. You may have questionsBlock_layout
The size of the structure is not32
, because the capture variable will be inblock
Internally generates a new variable, now captures a pointer type soBlock_layout
The total size40
bytes BLOCK_HAS_COPY_DISPOSE
=(1 < < 25)
.flags
&BLOCK_HAS_COPY_DISPOSE
To determine if there isBlock_descriptor_2
BLOCK_HAS_SIGNATURE
=1 < < (30)
.flags
&BLOCK_HAS_SIGNATURE
To determine if there isBlock_descriptor_3
signature
The value is0x000000010038ffa6
, the printed value isv8@? 0
Add signature information
v8@? 0:v indicates that the return value is null, 8 indicates the total size of the parameter, and @? Indicates block, and 0 indicates the start of byte 0
Conclusion: LLDB debugging results and the above source code to explore the results of mutual verification
- global
block
Debug verification
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
void (^ block)(void) = ^{
};
block(a); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Debug at the beginning of the _Block_copy assembly or at the RET location
Block_layout
The size of the structure is zero0x0000000000000020
Converted to10
Into the system32
Because no variables are captured, the size is only32
- global
block
There is noBlock_descriptor_2
, there areBlock_descriptor_3
- Because memory is continuous
Block_descriptor_2
No, so0x0000000104a03f9b
isBlock_descriptor_3
The starting position of
Summary: With LLDB debugging, you now have a detailed understanding of the Block_layout structure
blcok
A three-layer copy of
_Block_copy
The source code to explore
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if(! arg)return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
// Whether the block needs to be released
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// Return if it is a global block
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {// stack-heap (compile time)// This can only be a stack block
// Heap blocks cannot be created at compile time, only stack blocks can be created, only heap blocks can be created by _Block_copy
// Its a stack block. Make a copy.
size_t size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);// Open up memory
if(! result)return NULL;
// copy aBlock to result
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)
// The BLOCK_SMALL_DESCRIPTOR contains Block_descriptor_1, Block_descriptor_2, and Block_descriptor_3
// It is determined by the flags
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
...// omit inside the descriptor is copy assignment
}
#endif
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
//result: block created in the heap aBlock: stack passed in from outside
// Call Block_descriptor_2 'copy'
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;// Set isa bit _NSConcreteMallocBlock to heap block
returnresult; }}Copy the code
- if
block
If you need to release it, release it - if
block
Is a globalblock
, no operation is required to return directly - Because the heap
block
You need to allocate memory in the heap area, which is not heaped at compile timeblock
, so it can only be the stackblock
- through
malloc
Creating new memory - through
memmove
The stack areablock
The data is copied to the newly created memory - through
_Block_call_copy_helper
callBlock_descriptor_2
In thecopy
methods - On the heap
block
theisa
Set to_NSConcreteMallocBlock
Summary: _Block_copy copies blocks from the stack to the heap
_Block_call_copy_helper
The source code to explore
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{ // Get the pointer to copy
if (auto *pFn = _Block_get_copy_function(aBlock))
// Call copy
pFn(result, aBlock);
}
Copy the code
- through
_Block_get_copy_function
Methods to obtaincopy
Function pointer to a function - And then call
copy
function
Explore the _Block_get_copy_function method
// Get a pointer to the method
#define _Block_get_relative_function_pointer(field, type) \
((type)((uintptr_t)(intptr_t)(field) + (uintptr_t)&(field)))
#define _Block_get_function_pointer(field) \
(field)
static inline __typeof__(void(*) (void *, const void *))
_Block_get_copy_function(struct Block_layout *aBlock)
{ // No copy method returns NULL if Block_descriptor_2 is not present
if(! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE))return NULL;
// Get the first address of Block_descriptor_1
void *desc = _Block_get_descriptor(aBlock);
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
// If Block_descriptor_1, Block_descriptor_2, and Block_descriptor_3 are all present
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);
}
// _Block_get_function_pointer is a macro
// _Block_get_copy_fn Gets the pointer to the copy function
_Block_get_copy_fn(struct Block_descriptor_2 *desc)
{
return (void(*) (void *, const void *))_Block_get_function_pointer(desc->copy);
}
Copy the code
- Determine if there is
Block_descriptor_2
If there is no returnNULL
- If you have
Block_descriptor_2
, depending on the situation to getcopy
Function pointer to a function
Summary: the _Block_call_copy_helper method is used to get a pointer to copy and call copy
Note: The copy function is the copy function in Block_descriptor_2
_Block_object_assign
The source code to explore
When block structures in main. CPP are initialized, the descriptor in the structure is assigned with parameters passed in
The figure shows that the copy variable in the block structure stores the address of the __main_block_copy_0 function. Calling copy in the block structure calls the __main_block_copy_0 function
static void __main_block_copy_0(struct __main_block_impl_0*dst,
struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}
Copy the code
__main_block_copy_0
The function takes two argumentsdst
andsrc
.dst
Is the heap areablock
.src
Is the stack areablock
- In the inquiry
_Block_copy
methods_Block_call_copy_helper(result, aBlock)
Method is calledcopy
Method,result
Is the heap areablock
.aBlock
Is the stack areablock
__main_block_copy_0
Function is called_Block_object_assign
Methods._Block_object_assign
There are three parameters. The first two parameters areblock
The captured variable. The third parameter captures the type of the variable
Explore the types of captured variables
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
Common types
BLOCK_FIELD_IS_OBJECT
, the variable type isOrdinary objects
BLOCK_FIELD_IS_BLOCK
, the variable type isblock
typeBLOCK_FIELD_IS_BYREF
, the variable type is__block
Modify variables
Explore the _Block_object_assign method and search globally for _Block_object_assign in libclosure-79
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:
// _Block_retain_object_default = fn (arc)
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*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:
*dest = object;
break;
default:
break; }}Copy the code
- If the variable type is
Ordinary objects
Then, the pointer is copied to the system*dest
=object
, reference counting+ 1
- If the variable type is
block
Type, proceed_Block_copy
operation - If the variable type is
__block
Modify variables to perform_Block_byref_copy
operation
_Block_byref_copy
The source code to explore
_Block_byref_copy is a copy of variables. Variables modified by _block are compiled into Block_byref type
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
// __block -> {}
/ / structure
struct Block_byref {
void * __ptrauth_objc_isa_pointer isa; / / 8
struct Block_byref *forwarding; / / 8
volatile int32_t flags; // contains ref count//4
uint32_t size; / / 4
};
struct Block_byref_2 {
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
const char *layout;
};
Copy the code
__block
The modified variable is compiled underneathBlock_byref
Structural typeBlock_byref
The type andBlock_layout
thedescriptor
It’s similar. It’s all throughflags
To judgeBlock_byref_2
andBlock_byref_3
Whether there is. bothBlock_byref_2
andBlock_byref_3
It’s optional. You can refer to pairsBlock_layout
Analyze to understand by analogyBlock_byref
The _Block_byref_copy method is explored
static struct Block_byref* _Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// Create a copy of external variables in the heap
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// Copy and SRC forwarding refer to the same memory
// This means that they hold the same object
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
// This is similar to _Block_copy
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1); copy3->layout = src3->layout; } call the byref_keep method in Block_byref_2 (*src2->byref_keep)(copy, SRC); }else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src)); }}// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
Copy the code
- through
malloc
Method open up a copy of the external variables and store them in the newly opened memory of the heap copy->forwarding
=copy
和src->forwarding
=copy
Indicating that theirforwarding
Points to the same piece of memory, so they hold the same object(*src2->byref_keep)(copy, src)
callBlock_byref_2
In thebyref_keep
methods
Summary: _Block_byref_copy copies __block modified variables, that is, Block_byref copies
byref_keep
The source code to explore
The byref_keep method is initialized when the Block_byref_2 structure is compiled at the bottom. Look at the main.cpp file
The main. CPP file shows that byref_keep stores the __Block_byref_id_object_copy_131 function pointer in the Block_byref_2 structure. Byref_destroy stores the __Block_byref_id_object_dispose_131 function pointer
__Block_byref_id_object_copy_131 calls the _Block_object_assign method again, this time using the obJ object in the Block_byref structure. So the BLOCK_FIELD_IS_BLOCK process will be used this time
Conclusion:
- through
_Block_copy
Method to stack the areablcok
Make a copy and put it in the heap __block
Modifies the object through_Block_byref_copy
Methods,Block_byref
Structure type is copied- through
_Block_object_assign
Methods,Block_byref
Object processing in. This layer is not actually copied, but it is still copied
Note: Only blCOk-modified objects have three levels of copy. The ability to copy depends on the type of variable being captured
_Block_object_dispose
To explore the
Are the _Block_object_dispose method and _Block_object_assign method corresponding? Please compile and check the same method
The assembly shows that the _Block_object_dispose method is called when the block is released. Search globally for _Block_object_dispose in libclosure-79 source code
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break; }}Copy the code
The _Block_object_dispose method calls disponse at Block_descriptor_2 of the block structure, and does different releases according to the captured variable type _Block_object_dispose. The _Block_byref_release method is called if the variable is __block decorated
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
//byref_destroy releases the destroy variable corresponding to byref_keep
(*byref2->byref_destroy)(byref);
}
/ / release
free(byref); }}}Copy the code
The _Block_byref_release method is the release and destruction of objects and variables
conclusion
Block source code is not a lot, but the process is very round, sometimes a little confused. I also explored many times in the process of exploration before I slowly clarified the whole logic. Patience and persistence are important