1. What is block

In everyday iOS development, we often see code like this:

These code blocks enclosed in braces for ‘^{}’ or ‘^(){}’ are called blocks.

2. The use of the block

2.1 Block Settings

There’s a global variable ‘aBlock’ set here, and it’s set in viewDidLoad

2.2 Invocation of block

The block is called when the touchesBegan is clicked on the screen

2.3 summarize

Blocks can be set in one place and called in another, making them very flexible.

3. The classification of the block

We print three blocks with the following code

  • 1. Block1 does not capture external variables. It is of type __NSGlobalBlock__ and belongs to the global block.
  • Block2 captures the external variable and strongly references it. The variable block2 is of type __NSMallocBlock__ and belongs to the heap block.
  • 3. Block3 captures the external variable, but the variable block3 weakly references it. It is of type __NSStackBlock__ and belongs to the stack block.

4. Block’s circular reference solution

4.1 Why does circular Reference Occur

4.1.1 Normal Release

Normal release means that object A holds object B, and A calls the Dealloc method when releasing and sends A release signal to B. After B receives the signal, retainCount reference count -1; If B’s reference count is zero at this point, B also calls the Dealloc method to release.

4.1.2 Circular Reference

A circular reference means that object A holds object B, and B holds object A; So A’s reference count is always greater than zero, so A can’t call Dealloc and send RELEASE to B; Similarly, B’s reference count is always greater than 0, so it cannot call Dealloc and send release to A. Circular references cause two objects to hold each other previously and cannot be freed.

4.2 How can I Solve the Problem of Circular Reference

A circular reference to a block is held by an object and the block

Analysis:

  • 1. Break self’s strong reference to block; This is not feasible, because if you break this relationship, there are no objects holding blocks, and blocks are destroyed as soon as they are created;
  • 2. Break a block’s strong reference to self in this way.

4.2.1 Mode 1: Automatic Release

  • 1. This is what we call a dance between strength and weaknessweak-strong-dance;
  • 2. The __weak modifier weakSelf will be added to oneA weak reference tableMedium, weakSelf and self will pointSame memory addressIt’s not going to cause selfReference count +1, and weakSelf will release automatically;
  • 3.__strong Modified strongSelf is a temporary variable strongly referenced to weaSelf when the block is finished executingAutomatic releaseIf strongSelf is not declared here, weakSelf printed by NSLog will become nil when dealloc is called and dispatch_after is executed.

At this point, the holding relationship of self, block, weakSelf and strongSelf is shown as follows:

This breaks the block’s strong reference to self and relies on the mediator mode. When the block is done,strongSelf is automatically set to nil, freeing self. This mode is automatic release.

4.2.2 Mode 2: Manual Release

__block changes a temporary variable vc to hold self,block holds VC, and when the block is done, vc is released, breaking the block’s strong hold on self

4.2.3 Method 3: Pass self as an argument to the block

If you pass self as an argument to a block, the block will not hold self, and there will be no problem with circular references.

5. Underlying principles of Block

5.1 The nature of blocks

1. Implement the following blocks

2. Open the terminal, and run the CD command to go to the directory where the main.m file is storedclangThe commandxcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.mCompile the main.m file and getmain.cppfile

3. Open the main. CPP file and find the implementation of the main function

You can see that block = &__main_block_IMPL_0, passing in two arguments (__main_block_func_0, &__main_block_desc_0_data), refers to a function pointer address

4. Check the implementation of __main_block_IMPL_0. The passed parameters __main_block_func_0 and __main_block_desc_0_DATA

  • As can be seen from the picture above__main_block_impl_0It’s actually a structure that has two propertiesimplandDesc, and a construction function of the same name__main_block_impl_0;
  • 2. Parameter 1__main_block_func_0Is assigned to__main_block_impl_0 functionthefpAnd is finally assigned toimpl.FuncPtr;
  • Inpl. isa = &_nsConcretestackBlock;
  • 4. The parameter 2__main_block_desc_0_DATAIs assigned toDesc.

So the essence of a block is an object, a structure, and a function. Since a block function has no name, it is also called an anonymous function and can also be called a closure.

5. What code does the block call compile into

FuncPtr(block), a function that passes a block when called, in the same way that all oc methods have ‘self’ and ‘sel ‘hidden arguments.

The code relationship for blocks before and after compilation is as follows

5.2 Why does block Need to be called

As we have seen from the previous block nature, when the block is created, the underlying structure is actually __main_block_IMPL_0, created by its constructor of the same name. The parameter 1, corresponding to the parameter fp, is assigned to impl->FuncPtr; So if the FuncPtr call is not executed, the block code will not be executed. The summary is as follows:

  • 1. Function declaration: A block declares a function inside__main_block_func_0The code for this function is block{}Code in;
  • 2. Function call: The function corresponding to the block is saved in the FuncPtr property, so you need to call the block with block->FuncPtr(block).

5.3 How does a block capture external variables

Compile the following code through clang into c++ code

There is a variable called a, so let’s see how variable A is captured by the block after compilation

  • 1. You can see the value of variable A passing through the function__main_block_impl_0A constructor of the same name is passed in for the block;
  • In 2.__main_block_impl_0The structure defines oneint a;To access the value of external variable A, so as to achieve the capture of external variables.

Conclusion:

Blocks capture external variables by defining the same type of namesake and variable in their corresponding structure.

5.4 __block principle

Implement the following code and execute it

The value of a variable modified by __block is changed outside the block after a++ inside the block.

Again, compile this code

  • 1. Can be found, by__blockThe modified a becomes compiled__Block_byref_a_0Structure of;
  • 2. A’s address is__Block_byref_a_0The type of__forwardingRecord;
  • 3. A value isint aTo receive.

Now look at the constructor of a block__main_block_impl_0An anonymous function corresponding to a block__main_block_func_0

  • 1. The structure corresponding to block is defined__Block_byref_a_0 *aProperty to receive external incoming_athe__forwardingPointer. That is to say,Block of aandExternal variable APoint to the same memory address;
  • 1. 2. Smart refrigerator__main_block_func_0In the function(a->__forwarding->a) ++;Is theThe address of the __block modifier variable AThe corresponding values are modified to operate on the same memory address. So when you add ++ to a inside the block, the outside of the block also receives a change in the value of A.

Conclusion:

  • The __block principle is to compile a variable modified by __block at compile time__Block_byref_ Variable name _0Type of structure;
  • Passes inside the block__Block_byref_ Variable name _0The type of__forwardingPointer to external__Block_byref_ Variable name _0Type variable;
  • Is passed when modifying a variable modified by __blockblock->a->__forwarding->aTo modify a variable;
  • Changes to __block-decorated variables pass both inside and outside the block__forwardingPointer finds the same memory address to modify.

5.5 The true type of the underlying block

Let’s explore the underlying type of a block using symbolic breakpoints and assembly to locate the source code

1. Break the point at the block and start assembly to run the code

2. Find through assembly analysisobjc_retainBlockTo press a symbolic breakpoint and run

3. Continue to open the _Block_copy symbol breakpoint and run the command

This leads us to the library libsystem_blocks. Dylib where the block source code resides.

Go to Apple’s open source site and download the latest libclosure-78 source code, open it and search for _Block_copy

As you can see from the _Block_copy implementation, the arg passed in is assigned to a block object of type Block_layout

Attributes in the _Block_copy structure:

1. Isa, which points to the class to which the block belongs;

Flag, which records some states of the block object, is an enumeration:

// Alone in the world // Values for Block_layout->flags to describe block objects enum {// Flags is passed to tell the block to release BLOCK_DEALLOCATING = (0x0001), and store the value of the reference count, Is an optional parameter BLOCK_REFCOUNT_MASK = (0xfffe), // runtime // if the lower 16 bits are valid, BLOCK_NEEDS_FREE = (1 << 24), // runtime // whether to have a copy assist function, (a copy helper function) decide block_description_2 BLOCK_HAS_COPY_DISPOSE = (1 << 25), Block C++ destructor BLOCK_HAS_CTOR = (1 << 26), // compiler: Helpers have C++ code helpers have C++ code OSX BLOCK_IS_GC = (1 << 27), // runtime // flag is global block BLOCK_IS_GLOBAL = (1 << 28), // Compiler // In contrast to BLOCK_HAS_SIGNATURE, check whether the current block has a signature, which is used to dynamically call BLOCK_USE_STRET = (1 << 29) at Runtime, // compiler: undefined if ! BLOCK_HAS_SIGNATURE = (1 << 30), // Compiler // Decide block_description_3 BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // Compiler};Copy the code

We focused on BLOCK_DEALLOCATING, BLOCK_HAS_COPY_DISPOSE, and BLOCK_HAS_SIGNATURE

3. Reserved: Reserved information. It can be understood that the reserved position is used to store information about internal variables of the block

4. Invoke: A function pointer to the block’s executing code

5. Descriptor: Reserved information of blocks, reserved bits, block size, copy and dispose auxiliary function pointer, which has three kinds:

The three descriptor values are as follows:

  • 1. Descriptor1 must be used, return descriptor1;
  • 2. Descriptor2 is throughflags&BLOCK_HAS_COPY_DISPOSEDetermining whether there is a value and obtaining it by memory translation;
  • 3. Descriptor3 is throughflags & BLOCK_HAS_SIGNATUREDetermining whether there is a value and obtaining it by memory translation;

Summary: The true type underlying a block is Block_layout.

5.6 Block memory changes

From the implementation of the _Block_copy function we can see:

  • 1. Release if you need to release.
  • 1. If a heap block is used, reture it directly;
  • The else case is stack block, because the heap block needs to allocate memory manually; The process here is to request a block of memory and pass the stack blockmemmoveCopy toBlock_layoutThe type ofresult Object and point isa to_NSConcreteMallocBlock.

Through source code analysis, we clearly see how blocks from stack blocks to heap blocks.

5.7 Block Signature

Look at the memory layout of Block_layout, you can get the property descriptordescriptor of Block_layout by shifting memory by 3*8, mainly to check whether Block_descriptor_2 and Block_descriptor_3 are present. Where the property Descriptor3 has the block’s signature.

Signature indicates the signature of a block.

5.8 Layer-3 Copy of blocks

5.8.1 Layer 1 Copy: _Block_copy

Here we copy stack blocks to heap blocks

5.8.1 Layer 2 Copy: _Block_object_assign

First of all, we need to know what kinds of external variables are available. The most commonly used are BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF

Enum {// implement for a more complete description of these fields and combinations // That is, no other reference types BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block... BLOCK_FIELD_IS_BYREF = 8, 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 // // called from __block (byref) copy/dispose support routines.};Copy the code

Analyze the source code for _Block_object_assign

  • 1.BLOCK_FIELD_IS_OBJECTIf it is a common object, it will be handed to the system arc processing, and copy the object pointer, that is, reference count +1, so the external variable can not be released;
  • 2.BLOCK_FIELD_IS_BLOCK: Passes if the variable is of block type_Block_copyOperation,Blocks are copied from the stack to the heap;
  • 3.BLOCK_FIELD_IS_BYREFCall if it is a __block decorated variable_Block_byref_copyFunction for memory copy and general processing;

The second copy that occurs here is in the _Block_byref_copy function, whose source code is analyzed below

Static struct Block_byref *_Block_byref_copy(const void *arg) {// struct Block_byref * SRC = (struct Block_byref * struct) Block_byref *)arg; if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) { // src points to stack 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 ->forwarding = copy; // patch heap copy to point to itself src->forwarding = copy; // patch stack to point to heap copy copy->size = src->size; 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); 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; } (*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

The Block_byref type is the compiled true type of the __block-modified variable. Copy the arG passed in from the outside into ‘copy’ and use the __forwarding pointer to point to the same memory address. Here is the copy of the external variable

5.8.3 Layer 3 Copy

Define a __block decorated NSString object

__block NSString *name = [NSString stringWithFormat:@"xjh"]; void (^block1)(void) = ^{ // block_copy lg_name = @"xjh"; NSLog(@"xjh - %@",lg_name); // block memory}; block1();Copy the code

Xcrun compiles as follows,

  • There are more compiled names than normal variables__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131;
  • __Block_byref_cjl_name_0I have a lot of structures__Block_byref_id_object_copyand__Block_byref_id_object_dispose;
/ / * * * * * * * * the name of the compiled * * * * * * * * __Block_byref_name_0 name = {(void *) 0, (__Block_byref_name_0 *) & name, 33554432, sizeof(__Block_byref_name_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...) )(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_9f330d_mi_0)}; / / * * * * * * * * __Block_byref_name_0 structure * * * * * * * * struct __Block_byref_name_0 {void * __isa; __Block_byref_name_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); // 5*8 = 40 NSString *name; }; / / * * * * * * * * __Block_byref_id_object_copy_131 * * * * * * * * / / block their own copies (_Block_copy) - __block bref structure copy (_Block_object_assign) Static void __Block_byref_id_object_copy_131(void * DST, Void * SRC) {// DST external capture variable, that is, structure - 5*8 = 40, Name (name was assigned when bref was initialized) _Block_object_assign((char*) DST + 40, *(void * *) ((char*) SRC + 40), 131); } //********__Block_byref_id_object_dispose_131******** static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); }Copy the code

Use libclosure-74 to debug the compilable source code breakpoint. The key methods are executed in the following order: _Block_copy -> _Block_byref_copy -> _Block_object_assign

So how does a block get a name?

  • 1. Run the _Block_copy method to copy a block to the heap.
  • 2. Copy using the _Block_object_assign method because the __block variable is the underlying Block_byref structure;
  • 3. Find that the external variable is a __block modified object, copy the corresponding object name from bref to the block space, then use it. * block_object_assign (*dest = object; * block_object_assign); See).

Conclusion:

  • [Level 1] Through_Block_copyImplements a copy of the object itself fromThe stack areaCopy toThe heap area;
  • [Layer 2] Through_Block_byref_copyMethod to copy the compiled structure object of the true type of the external __block modified object asBlock_byrefStructure type;
  • [Level 3] call_Block_object_assignMethod, a copy of the actual type variable modified by __block.

6. Summary

In daily development, we should first fully understand the block, proficient in the use of block, especially the solution of circular reference. Understanding the nature of blocks and the underlying principles helps us write high-quality code and use blocks flexibly.

7. Reference: Great God of The Moon’s blog:IOS – Basic Principles 30: Block basic principles