Introduction to analysis
In the iOS development process will inevitably use block, today to explore its underlying structure and principle, the next first on a simple code
Use xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m to convert the main.m file into a C++ file. Since the file is large, only the key content will be selected for analysis
Block ->FuncPtr(block) (FuncPtr(block)); the structure of our block is the __main_block_IMPL_0 structure. There are three structure members:
__block_impl
Type:impl
__main_block_desc_0
Type:Desc
int
Type:age
The __block_impl structure is as follows:
You can see that one member variable FuncPtr is a member variable of type pointer
The __main_block_desc_0 structure is as follows:
The main thing here is the size of memory required to record blocks
Go back to the first line of code in the main function
We call the constructor of the __main_block_IMPL_0 structure to create the block, and see that the first argument __main_block_func_0 passed in is a function pointer
Assign this function pointer to FuncPtr in the constructor, so subsequent calls will be block->FuncPtr(block) so called.
structure
Summarizing the above explanation, you might get something like this
The nature of the block
block
The essence is also oneOC
Object, because it also has one insideisa
Pointer to theblock
Encapsulates a function call and its environmentOC
object
The underlying structure of block is as follows:
Block variable capture
Auto Local variable capture
To explore why this is the case, use the command xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m
You can see that in the __main_block_IMPL_0 constructor, the age variable passed in from the outside is assigned to the age member variable in the structure
Tip
C++ syntax constructors use default assignments, such as __main_block_impl_0(void *fp, struct _main_block_desc_0 *desc, int _age, int flags=0) : Age (_age), which assigns the external parameter _age to the member variable age
Static Local variable capture
And if so, what is the answer? Use the same command xcrun-sdk iphoneOS clang-arch arm64-rewrite-objc main.m
It’s a little bit different than what we saw before, where we just passed in the value, we passed in the address, and then we go down
You can see it’s all passing Pointers, and this pointer points to the address of age, so the answer should be 25
Capture of global variables
Use the xcrun-sdk iphoneOS clang-arch arm64-rewrite-objc main.m command to check
You can see that there is no capture at this point
conclusion
Variable types | Inside the block | access |
---|---|---|
Auto local variable | ✅ | Value passed |
Static local variable | ✅ | Pointer passed |
The global variable | ❌ | Direct access to the |
Three types of blocks
Block of copy
In an ARC environment, the compiler automatically copies blocks on the stack to the heap as necessary, as follows:
block
As a function return value- will
block
Assigned to__strong
When the pointer block
As aCocoa API
The Chinese legal name containsusingBlock
(ex:NSArray sort method)block
As aGCD
API method parameter
The auto variable of object type
The variables studied from the beginning have been primitive data types. Now what happens if the external object is an object type
You can see that __main_block_DESc_0 has internal copy and dispose, which is convenient for external variables to use _Block_object_assign to retain external objects when block objects are copied from stack to heap
So what happens if it’s a weak reference? Use xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m, but you’ll find an __weak error that requires runtime. So a new parameter is added to specify the runtime version to use
Xcrun-sdk iphoneos clang-arch arm64-rewrite-objc-fobjc-arc-fobjc-runtime =ios-13.0.0 main.m
When a block accesses the auto variable of the object type internally:
-
If the block is on the stack, there is no strong reference to the auto variable
-
If a block is copied to the heap
- Will be called
block
The inner copy function - Copy is called internally
_Block_object_assign
function _Block_object_assign
The function is based on the variable modifier (__strong
,__weak
,__unsafe_unretained
) to make the corresponding operation, similar toretain
- Will be called
-
If a block is removed from the heap
- Will be called
block
The inside of thedispose
function dispose
The function will call_Block_object_dispose
_Block_object_dispose
The reference will be released automaticallyauto
Variable, something like thatrelease
- Will be called
__block qualifier
We all know that if we want to change a captured external variable in a block, we cannot change it directly.
So why on earth can’t it be changed? Again, you see the analysis
The age variable is scoped in the main function, changing the age value occurs in the __main_block_func_0 function, and the age used in this function is not the age variable but the variable of the block record, so we can think of static local variables. Of course you can use a global variable, but the downside is that the variable will always be in memory, so the __block modifier is introduced. Let’s see, what does __block do
As you can see, age is wrapped as an object of type __Block_byref_age_0. This structure is basically as follows
Memory management of __block
- when
block
When it’s on the stack, it doesn’t__block
Variables generate strong references - when
block
Is copied to the heap-
The copy function inside the block is called
-
Copy calls the _Block_object_assign function internally to make a strong reference to the __block variable
-
- when
block
When it’s removed from the heap- Will be called
block
internaldispose
function dispose
Function internal call_Block_object_dispose
function_Block_object_dispose
The function will release the reference automatically__block
variable
- Will be called
Object type local variable &__block variable
- when
block
When on the stack, there is no strong reference to either of them - when
block
When you copy it to the heap, it all passescopy
Function to process _Block_object_assign
The function is based on the modifier of the object it points to (__strong
,__weak
,__unsafe_unretained
) perform the corresponding operations to form strong references or weak references (onlyARC
whenretain
.MRC
When not)- when
block
When it’s removed from the heap, it goes throughdispose
Function to release
The __forwarding pointer in __block
From the previous analysis, we saw that variables decorated with __block are wrapped into a new object with a __forwarding pointer. It was strange at the time why external variables should be accessed through this pointer. The main reason is to ensure that objects accessed by __forwarding Pointers are actually on the heap when a block is copied onto the heap.
Resolving circular references
The problem of circular reference often occurs in the use of blocks. The essence of this problem is that the objects held by each other cannot be released properly, which is divided into ARC environment and MRC environment
ARC
There are two solutions in the ARC environment:
__weak
,__unsafe_unretained
- use
__block
Solution (Disadvantage: must be calledblock
)
This principle is also simple, because the structure generated by the __block variable is strongly referenced by the block object. The structure object also strongly references self, which also strongly references the block, forming a triangular reference. The final null will break the triangular reference, thus breaking the circular reference.
MRC
__block-modified object types don’t strongly reference external variables when the block is copied to the heap. As a rule, MRC uses __unsafe_unretained and __block to break the loop.