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_implType:impl
  • __main_block_desc_0Type:Desc
  • intType: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

  • blockThe essence is also oneOCObject, because it also has one insideisaPointer to the
  • blockEncapsulates a function call and its environmentOCobject

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:

  • blockAs a function return value
  • willblockAssigned to__strongWhen the pointer
  • blockAs aCocoa APIThe Chinese legal name containsusingBlock(ex:NSArray sort method)
  • blockAs aGCDAPI 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

    1. Will be calledblockThe inner copy function
    2. Copy is called internally_Block_object_assignfunction
    3. _Block_object_assignThe function is based on the variable modifier (__strong,__weak,__unsafe_unretained) to make the corresponding operation, similar toretain
  • If a block is removed from the heap

    1. Will be calledblockThe inside of thedisposefunction
    2. disposeThe function will call_Block_object_dispose
    3. _Block_object_disposeThe reference will be released automaticallyautoVariable, something like thatrelease

__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

  • whenblockWhen it’s on the stack, it doesn’t__blockVariables generate strong references
  • whenblockIs copied to the heap
    1. The copy function inside the block is called

    2. Copy calls the _Block_object_assign function internally to make a strong reference to the __block variable

  • whenblockWhen it’s removed from the heap
    1. Will be calledblockinternaldisposefunction
    2. disposeFunction internal call_Block_object_disposefunction
    3. _Block_object_disposeThe function will release the reference automatically__blockvariable

Object type local variable &__block variable

  • whenblockWhen on the stack, there is no strong reference to either of them
  • whenblockWhen you copy it to the heap, it all passescopyFunction to process
  • _Block_object_assignThe 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 (onlyARCwhenretain.MRCWhen not)
  • whenblockWhen it’s removed from the heap, it goes throughdisposeFunction 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__blockSolution (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.