Take a few minutes to look at the following three questions and write down your answers. I gave these three small questions to three friends to answer before compiling this blog post. The result was that except for one friend who answered all 3 questions correctly, the other two friends only answered 1 question each. This shows that many iOS users still do not have a thorough understanding of Blocks. This blog post will explain Blocks in detail. 1 Simple rules for Block usage First understand the simple rules, then analyze the principles and implementation: In a Block, the Block expression captures the value of the automatic variable used, that is, saves the instantaneous value of the automatic variable. When a variable modified as __block is captured, the instantaneous value is no longer obtained. As for why, I will continue to talk about it later. 2 Block Implementation Block is an anonymous function with automatic variables (local variables). Block expressions are very simple and can be generally described as: "^ return value type parameter list expression". But Block is not a syntax unique to Objective-C. What’s going on? The clang compiler provides programmers with a way to understand the mechanism behind Objective-C. Through clang's conversion, you can see the implementation principle of Block. By clang -rewrite-objc yourfile.m clang will convert the Objective-C code into C language code. 2.1 Analysis of basic implementation of Block Use Xcode to create a Command Line project and write the following code:
Convert with clang: The above is the converted code. Don’t be confused, read it section by section. It can be seen that the content inside the Block has been converted into a common static function __main_func_0. Look at other parts: main.cpp __block_impl:
The __block_impl structure includes some flags, variables reserved for future version upgrades, and function pointers. main.cpp __main_block_desc_0:
The __main_block_desc_0 structure includes variables and block sizes reserved for future version upgrades. main.cpp __main_block_impl_0: The __main_block_impl_0 structure contains two member variables, namely the __block_impl and __main_block_desc_0 instance variables. In addition, there is a constructor. The constructor is called in the main function as follows: main.cpp __main_block_impl_0 constructor call:
Remove all kinds of forced conversions and simplify: main.cpp __main_block_impl_0 The constructor call is simplified:
The above code assigns the pointer of the __main_block_impl_0 structure instance to the variable blk of the __main_block_impl_0 structure pointer type. This is our initial structure definition:
In addition, there is another section in the main function:
Remove various conversions:
Actually, it is the original:
All the code in this section is in block_implementation (https://github.com/summertian4/iOS-ObjectiveC/tree/master/ObjcMemory/ObjcMemory-Test-Code/block_implementation) 2.2 Analysis of the implementation of Block intercepting the instantaneous value of external variables In 2.1, clang conversion was performed on the simplest parameterless block declaration and call. Next, let's look at a section of code for "intercepting automatic variables" (you can use the command clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.7 main.m):
After clang conversion: Compared with the conversion code in Section 2.1, we can find that there are some more codes. First, __main_block_impl_0 adds a variable val and adds the value of val to the constructor's parameters: main.cpp __main_block_impl_0:
In the main function, the declaration of Block becomes this sentence: main.cpp __main_block_impl_0 constructor call:
Remove the conversion: main.cpp __main_block_impl_0 The constructor call is simplified:
_Therefore, when Block is declared, Block has saved val as an internal variable of __main_block_impl_0. No matter how the value of val is changed after the declaration, it will not affect the internal value of val accessed when Block is called. This is the principle of Block capturing the instantaneous value of the variable. _ All the codes in this section are in EX05 2.3 Analysis of __block variable access implementation We know that a local variable can be read but not changed in a Block. If you try to change it, Xcode will prompt you that you cannot change the variable inside a Block. The local variables in a Block are read-only, but the Block can read and write the following variables:
This means that the following code is fine:
If you want to write local variables inside a Block, you need to add the __block modifier to the local variables you want to access. The __block modifier is similar to the static, auto, and register modifiers in C. It is used to specify the storage domain where the variable value is set. We can write code to test what changes are made after __block: EX07:
After clang conversion: Compared with 2.2, it seems that a lot of code has been added. Two more structures have been found. main.cpp __Block_byref_val_0:
I was surprised to find that the val of block type became an instance of the structure Block_byref_val_0. This instance contains the isa pointer, a flag, and a size. Most importantly, there is an additional forwarding pointer and val variable. What's going on? In the main function, the structure is instantiated: main.cpp main.m part:
We can see that when the structure object is initialized:
In the main function, the assignment statement val = 1 becomes: main.cpp val = 1; corresponding function
The essence can be seen here, val = 1, actually changes the val variable pointed to by the __forwarding pointer (that is, itself) in the __Block_byref_val_0 structure instance val. The same is true for accessing val. You can think of it as changing the value of a variable by taking its address, which is similar to changing a variable by taking its address in C language. Therefore, the variables in the declaration block can be changed. As for the other great effects of forwarding, we will continue to analyze them. This section of the code is in EX05 3 Block storage domain There are three types of Blocks:
__NSConcreteGlobalBlock appears in:
If the scope of the variable to which the block is set on the stack ends, the block will be discarded. If the block is used in it, the variable scope to which the block belongs will also be discarded when the scope ends. To solve this problem, the Block needs to be moved from the stack to the heap when necessary. When ARC is effective, in many cases, the compiler will help complete the copy of the Block, but in many cases, we need to copy the Block manually. When copying blocks in different storage domains, the impact is as follows: When copying, the impact on the accessed __block type object is as follows: At this point, we can see the great role of __forwarding - no matter whether the Block is in the heap or in the stack, since __forwarding points to the real address of the structure instance converted from the local variable, correct access can be ensured. Specifically:
As mentioned above, the compiler will help complete some block copying, and there are also manual block copying. Then the block is copied to the heap in the following situations (this paragraph is excerpted from "Objective-C Advanced Programming iOS and OS X Multithreading and Memory Management"):
4 Block Circular Reference Block circular reference is a very common problem in programming. Many times, we don’t even know that a circular reference has occurred until we suddenly find out one day that “this object does not call delloc” and then realize that there is a problem. It is also explained in "Block Storage Domain" that Block will retain the __block object once after copying it. Then a circular reference will occur in the following situations:
Since self -> blk, blk -> self, neither side can be released. But please note that circular references can also occur in the following cases:
This is due to self -> obj, self -> blk, blk -> obj. This situation is very easy to be overlooked. 5 Review Questions Let’s look at the first few small topics: ***question: Since the Block captures the instantaneous value, the output is in block val = 0 Question 2: Since val is __block, external changes will affect internal access, so the output is in block val = 1 Question 3: Similar to the second question, val = 1 can affect the internal access of the Block, so first output in block val = 1, then change the val value inside the Block, and output after block val = 2 when accessing again. Other I wrote this article after reading the book "Objective-C Advanced Programming iOS and OS X Multithreading and Memory Management". A lot of the content in this blog post is also derived from "Objective-C Advanced Programming iOS and OS X Multithreading and Memory Management". I highly recommend this book to everyone. This book records in-depth content about iOS memory management. But please note that many knowledge points in this book are not very detailed, and you need to learn with an open mind. In places where the explanation is not detailed, take the initiative to explore, expand, and find more information. Finally, you will find that you have a deeper understanding of iOS memory management. For the test code in the article, all are at (https://github.com/summertian4/iOS-ObjectiveC/tree/master/ObjcMemory). |
<<: 10 Practical Tips for iOS (There are always things you don’t know and things you will use)
>>: iOS Advanced - iOS Memory Management
With the continuous advancement of manned space t...
As the northern hemisphere enters winter, a spect...
Key Points ★ Bulk raisins can be washed, but rais...
01. The History of Content Marketing The term con...
As long as you have some experience in making mon...
After a Spring Festival, Looking at the flesh on ...
...
When you are unilaterally deleted by a friend on ...
Now is the season when fruits and melons are frag...
With the rapid development of the mobile Internet...
There is a saying in the folk music circle: It wo...
We have already entered the era of big data. The ...
At the beginning of autumn, let’s talk about “aut...
Some time ago, a friend of mine on Xiaohongshu to...