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

blocktype

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

blockAnalysis of the underlying

Block underlying analysis through the clang generated. CPP file to view the underlying compiled source code

blockCatch ordinary variables (not__blockModified)

  • blockCapture 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

  • blockCapture 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

  • rightblockIs initialized and assigned to the structure of__main_block_func_0Task function function,&__main_block_desc_0_DATAThe address of the function that describes the information and the external variablesobj
    • blockThe underlying layer is compiled to a structure type
    • blockThe structure now automatically generates oneNSObject *__strong objWhen assigning to the structureobj(_obj)throughC++Way toblockStructure of theobjThe assignment
    • will__main_block_func_0Assigned toblockVariables in a structure__block_implIn theFuncPtrsave
    • will&__main_block_desc_0_DATAAssigned toblockVariables in a structure__main_block_desc_0In theDescsave
  • callblcokIn the structure__block_implVariables in theFuncPtrVariables perform task function functions
    • FuncPtrWhen executed, theblcokAs a parameter, because we want to getblcokIn theobjcvariable
    • __main_block_func_0The variables used in the method are actually retrievedblcokNewly generated in the structureobj. In structureobjAnd external variablesobjIt points to the same memory space
  • blcokBoth assignment and execution are passedblcokInternal data or functions to execute, are usedblcokData 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

blockcapture__blockModified variable

  • blockcapture__blockModified 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 *)&objAssigned to__forwardingIn fact, it is__forwardingPoint to the__Block_byref_obj_0Address. In simple terms__Block_byref_obj_0In the__forwardingPoint to the__Block_byref_obj_0
  • __Block_byref_id_object_copy_131Assigned to__Block_byref_id_object_copy
  • __Block_byref_id_object_dispose_131Assigned 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- >objtheblcokIn structureobjAssigned toobj, that is, the__Block_byref_obj_0The address assigned to__Block_byref_obj_0 *obj
  • obj->__forwardingPoints to the__Block_byref_obj_0Address of the structure
  • obj->__forwarding->objis__Block_byref_obj_0In structureNSObject *__strong objvariable
  • obj->__forwarding->objModify theobjWhen,blockThe orientation of the internal and external variables is not changed, but the orientation of the internal and external variables is changedobjActually accessedobjIt’s the same thing, regardless of thatobjHas it been modified?

The above explored captured __block modified object types, and the following explored captured non-object types

  • blockcapture__blockModified 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:

  • __blockThe modified variable generates an underlying one__Block_byref_obj_0The structure of the body
  • __Block_byref_obj_0The structure holds the address of the object as well as__Block_byref_obj_0address
  • blcokBoth internal and external variables are pointing__Block_byref_obj_0Address. while__Block_byref_obj_0The variables in theobjWhen it changesblcokInternal and external variables still point to__Block_byref_obj_0Address, and then getobj

__blcokModify the variable indicator diagram

According to the.cppA graph drawn by the assignment and modification process of variables in a file. Just to make sense of it__blcokWhy can decorated variables be modified

blockNo variables captured (global or static)

Blocks capture uncaptured variables in the same way they capture global and static variables

  • blockUncaptured 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

  • blockCapture 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

blockThe 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

globalblockThe 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_retainBlockThe next step in assembly jumps to_Block_copy
  • objc_retainBlockMethod is inlibobjc.A.dylibThe source code in the library
  • In a real situationx0The first parameter of the method is the receiver of the message. throughlldbDebugging finds that the message receiver at this point isblock. And at this pointblockIt’s a globalblock

Add a symbolic breakpoint to _Block_copy and continue debugging

  • _Block_copyMethods in thelibsystem_blocks.dylibThe source code in the library
  • throughlldbDebugging found_Block_copyThe message receiver isblock. At this timeblockIt’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 areablockThe 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 areablockThe underlying exploration of

To stack areablcokSet 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

  • globalblockCalled at run time_Block_copyMethod is still globalblock
  • The heap areablockThe stack area is determined by compile timeblockCalled at run time_Block_copyMethod to generate a new heap areablock
  • The stack areablcokNot to_Block_copyThe 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

blockThe type ofBlock_layoutThe 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

  • isaSaid:blockType (stack, heap, global)
  • flags: identifiers are similar to those in objectsisathebits
  • reserved: Reserved field
  • invoke: function pointer, which holds the implementation address of the task function.cppIn the fileFuncPtrfunction
  • descriptor: 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 first1positionBLOCK_DEALLOCATING: Release flag, – commonBLOCK_NEEDS_FREEDo bits and operations, passed in togetherFlagsTo inform theblockCan release
  • low16positionBLOCK_REFCOUNT_MASK: Stores the reference count value, which is an optional parameter
  • The first24positionBLOCK_NEEDS_FREELow:16A valid flag by which the program decides whether to increase or decrease the value of the reference count bit
  • The first25positionBLOCK_HAS_COPY_DISPOSE: Whether the copy helper function is available (a copy helper function)
  • The first26positionBLOCK_HAS_CTOR: Owned or notblockThe destructor
  • The first27positionBLOCK_IS_GC: Indicates whether there is garbage collection; //OS X
  • The first28positionBLOCK_IS_GLOBAL: Indicates whether the flag is globalblock
  • The first30positionBLOCK_HAS_SIGNATUREAnd:BLOCK_USE_STRETIn contrast, judge the presentblockWhether you have a signature. Used forruntimeTime dynamic call

Among these tag bits, BLOCK_HAS_COPY_DISPOSE and BLOCK_HAS_SIGNATURE are particularly important

  • BLOCK_HAS_COPY_DISPOSEIndicates whether there areBlock_descriptor_2
  • BLOCK_HAS_SIGNATUREIndicates whether there areBLOCK_DESCRIPTOR_3
  • Why notBlock_descriptor_1Flag bit, becauseBlock_descriptor_1It’s a must
  • It can be determined by the marker bitBlock_descriptor_1It has to be,Block_descriptor_2andBLOCK_DESCRIPTOR_3Is 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_1Is the structure type, wherereservedIndicates reserved information,sizesaidblockThe size of the
  • Block_descriptor_2Is the structure type, wherecopyDeposit iscopyFunction address,disposeDeposit isdisposeFunction addresses
  • Block_descriptor_3Is the structure type, wheresignatureIndicates signature information.layoutsaidblockExtended 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

lldbvalidationBlock_layoutIn the variable

  • The heap areablockDebug 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

  • flagsThe value is0x00000000c3000002.descriptorThe value is0x0000000100390078.
  • printdescriptorThe information stored in thesizeThe value of the0x0000000000000028The conversion10Base is equal to the40isBlock_layoutThe size of the structure. You may have questionsBlock_layoutThe size of the structure is not32, because the capture variable will be inblockInternally generates a new variable, now captures a pointer type soBlock_layoutThe total size40bytes
  • BLOCK_HAS_COPY_DISPOSE=(1 < < 25).flags & BLOCK_HAS_COPY_DISPOSETo determine if there isBlock_descriptor_2
  • BLOCK_HAS_SIGNATURE=1 < < (30).flags & BLOCK_HAS_SIGNATURETo determine if there isBlock_descriptor_3
  • signatureThe 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

  • globalblockDebug 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_layoutThe size of the structure is zero0x0000000000000020Converted to10Into the system32Because no variables are captured, the size is only32
  • globalblockThere is noBlock_descriptor_2, there areBlock_descriptor_3
  • Because memory is continuousBlock_descriptor_2No, so0x0000000104a03f9bisBlock_descriptor_3The starting position of

Summary: With LLDB debugging, you now have a detailed understanding of the Block_layout structure

blcokA three-layer copy of

_Block_copyThe 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
  • ifblockIf you need to release it, release it
  • ifblockIs a globalblock, no operation is required to return directly
  • Because the heapblockYou need to allocate memory in the heap area, which is not heaped at compile timeblock, so it can only be the stackblock
  • throughmallocCreating new memory
  • throughmemmoveThe stack areablockThe data is copied to the newly created memory
  • through_Block_call_copy_helpercallBlock_descriptor_2In thecopymethods
  • On the heapblocktheisaSet to_NSConcreteMallocBlock

Summary: _Block_copy copies blocks from the stack to the heap

_Block_call_copy_helperThe 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_functionMethods to obtaincopyFunction pointer to a function
  • And then callcopyfunction

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 isBlock_descriptor_2If there is no returnNULL
  • If you haveBlock_descriptor_2, depending on the situation to getcopyFunction 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_assignThe 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_0The function takes two argumentsdstandsrc.dstIs the heap areablock.srcIs the stack areablock
  • In the inquiry_Block_copymethods_Block_call_copy_helper(result, aBlock)Method is calledcopyMethod,resultIs the heap areablock.aBlockIs the stack areablock
  • __main_block_copy_0Function is called_Block_object_assignMethods._Block_object_assignThere are three parameters. The first two parameters areblockThe 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 isblocktype
  • BLOCK_FIELD_IS_BYREF, the variable type is__blockModify 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 isOrdinary objectsThen, the pointer is copied to the system*dest = object, reference counting+ 1
  • If the variable type isblockType, proceed_Block_copyoperation
  • If the variable type is__blockModify variables to perform_Block_byref_copyoperation

_Block_byref_copyThe 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
  • __blockThe modified variable is compiled underneathBlock_byrefStructural type
  • Block_byrefThe type andBlock_layoutthedescriptorIt’s similar. It’s all throughflagsTo judgeBlock_byref_2andBlock_byref_3Whether there is. bothBlock_byref_2andBlock_byref_3It’s optional. You can refer to pairsBlock_layoutAnalyze 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
  • throughmallocMethod open up a copy of the external variables and store them in the newly opened memory of the heap
  • copy->forwarding = copysrc->forwarding = copyIndicating that theirforwardingPoints to the same piece of memory, so they hold the same object
  • (*src2->byref_keep)(copy, src)callBlock_byref_2In thebyref_keepmethods

Summary: _Block_byref_copy copies __block modified variables, that is, Block_byref copies

byref_keepThe 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_copyMethod to stack the areablcokMake a copy and put it in the heap
  • __blockModifies the object through_Block_byref_copyMethods,Block_byrefStructure type is copied
  • through_Block_object_assignMethods,Block_byrefObject 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_disposeTo 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