This is my 12th day of the August Challenge. In the last article we talked about the division of blocks and how to solve looping applications. In this article we will focus on the principle of block.

1. The Clang analysis

Define a c file about blocks:Compile using Clangclang -rewrite-objc block.c -o block.cpp

The equivalent of

void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); / / initialization
block->FuncPtr(block);// Call execution
Copy the code

Block is equivalent to __main_block_IMPL_0 being a function. Let’s look at what it’s made of

It’s a structure that containsimp.desc.ByRefExternal variables.

__block_implComposition: __main_block_desc_0Composition:

__BlockExternally decorated variables:__Block_byref_a_0Structure:Callback function implementation:__main_block_func_0Structure:

From Clang compilation we know: we use__blockThe modified block is oneThe structure of the bodyObject containing oneimplThe implementation of our functionality; Description of a block and handling of external variables. Their relationship is as follows:

1.1 Invocation of block

  ((void(*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); Equals the following block->FuncPtr(block);// Call execution
Copy the code

When we Clang, we know that the block’s code block, {}, is the __block_impl structure, which contains the address of the FuncPtr implementation, which points to __main_block_func_0. We call access through a pointer implementation. Blocks are stored functionally and cannot be implemented if not called, and cannot be called back if not called in daily development.

  • Function declarationThe internal implementation of a block is declared as a function__main_block_func_0
  • Perform specific function implementations: by calling a blockFuncPtrPointer to call block execution

1.2 Block captures external variables

// __block int a = 18;

    int a = 18;

    void(^block)(void) = ^ {// a++;

        printf("a-count - %d",a);

    };

     block();
Copy the code

The compiled:

struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  int a;

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};/ / implementation
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  int a = __cself->a; __cself->a; // bound by copy

        printf("a-count - %d",a);

    }
Copy the code

Here: a(_a) is C++ syntax. By default, it will assign a value to the parameter a, _a will pass it to a, and the assignment will be done, and the underlying block will capture the variable as its own member variable. The ain __main_block_func_0 is a copy of the value, and if a++ is used in the internal implementation of a block, it is problematic and will cause confusion in the compiler code, which means that a is read-only: when a block captures an external variable, it automatically generates the same attribute internally to save it

1.3 __block modification

int main(){

    __block int a = 18;

    int a = 18;

    void(^block)(void) = ^{

        a++;

        printf("a-count - %d",a);

    };

    

     block();

    return 0;

}
/ / the compiled
struct __Block_byref_a_0 {

  void *__isa;

__Block_byref_a_0 *__forwarding;//*__forwarding* this points to the address of A

 int __flags;

 int __size;

 int a;

};

struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  __Block_byref_a_0 *a; // by ref

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};/ / implementation
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  __Block_byref_a_0 *a = __cself->a; // the __cself->a is the same as the __cself->a.


        (a->__forwarding->a)++;

        printf("a-count - %d",(a->__forwarding->a));

    }
Copy the code

When initialized (__Block_byref_a_0 *)&a, take the address of a. __main_block_func_0 internal processing of a is a pointer copy, in which the created object A points to the same memory space as the passed object A. Conclusion: When we use __block to modify an external variable, we generate a __Block_byref_a_0 structure that holds the pointer and value of the original variable. Pass the pointer address of the structure object generated by the variable to the block, and then you can manipulate the external variable inside the block.

2. Block source code analysis

About compiled __main_block_copy_0, __main_block_dispose_0, __main_block_desc_0 these structures, they are used for what, let’s analyze the block copy process. Through breakpoints, we analyze the assembly process, debugging code is as follows

Breakpoint debugging starts for the stack block

callobjc_retainBlock We giveobjc_retainBlockAdd sign breakpoint

_Block_copySymbol breakpoint, run to break, atlibsystem_blocks.dylibIn the source

You can download the latest version from Apple’s open source websitelibclosure-79Source code, by viewing_Block_copySource code implementation,

2.1 Block_layout analysis

The true type of block found underneath isBlock_layout

  1. Contains a pointer to ISA, of type block
  2. Flags: The flags identifier is defined as

The specific explanation

  • Bit 1 – BLOCK_DEALLOCATING, freeing the mark, usually BLOCK_NEEDS_FREE does the bit and operation, passing it along with Flags to tell the block to be free.

  • Lower 16 bits – BLOCK_REFCOUNT_MASK, which stores the value of the reference count; Is an optional parameter

  • Bit 24 – BLOCK_NEEDS_FREE, which indicates whether the lower 16 is valid and determines whether to increase or decrease the value of the reference count bit;

  • Bit 25 – BLOCK_HAS_COPY_DISPOSE, whether it has a copy helper function;

  • Bit 26 – BLOCK_IS_GC, whether it has a block destructor;

  • 27th, indicating whether there is garbage collection; //OS X

  • Bit 28 – BLOCK_IS_GLOBAL, indicating whether a global block is present;

  • Bit 30 – BLOCK_HAS_SIGNATURE, as opposed to BLOCK_USE_STRET, determines whether the current block has a signature. Used for dynamic invocation at Runtime.

  1. Invoke a function
  2. Descriptor other related descriptions, whether destructor is being performed, etc

Descriptor: Additional information about a block, such as the number of variables to keep, the size of the block, and Pointers to auxiliary functions to copy or dispose. Have three kinds of

  • Block_descriptor_1Is choice
  • Block_descriptor_2Block_descriptor_3It’s all optional

2.2 Block memory changes

We break a global block

Read the register, at this timeblock 是Global block, i.e.,__NSGlobalBlock__type

Adding external variables

At this point the breakpoint of reading the block is still__NSStackBlock__

Continue with the process

  • increase_Block_copySign breakpoint and break, add breakpoint directly in the last RET, read RAx, found through_Block_copyAnd then, it becomes thetaHeap block, i.e.,__NSMallocBlock__Mainly becauseBlock addressChanged to a heap block

Conclusion: When a block contains external variables,compileWhen isStack block.The runtimethrough_Block_copyCopy the stack block toHeap area block.

2.3 the signature

We know above that additional information about Block_layout descriptor_1 is required and Block_descriptor_2 and Block_descriptor_3 are optional.

The storage is contiguic in memory, and we also get Block_descriptor_2 elsewhere in the source code by translation

Flag is BLOCK_HAS_COPY_DISPOSEdesc2 stores copy information. If flag is BLOCK_HAS_SIGNATURE, desc3 stores signatures. We use LLDB to verify:

We’re reading the memory of the heap block, and we know the structure of the block before so,0x000000010bff3028The corresponding isdescriptor

Desc1 is made up of resLoved and size. Desc1 is made up of resLoved and size. Desc1 is made up of resLoved and size0x000000010bff2de0And convertchar*Print out the previous signature. Desc2 does not exist.

  • Determine if there isBlock_descriptor_2, namely the flagsBLOCK_HAS_COPY_DISPOSE(copy auxiliary function) has a value

Block_layout->flagsDesc2 is only available for BLOCK_HAS_COPY_DISPOSE, so wep/x 1<<25, that is, 1 is shifted 25 bits to the left, and its hexadecimal is 0x2000000 A value of 0 indicates that desc2 is not present.

  • Verify Block_descriptor_3, flags isBLOCK_HAS_SIGNATUREThe presence of DESC3 stores the signature

If yes, DESC3 exists.

  • Verify signature:[NSMethodSignature signatureWithObjCTypes:"v8@?0"], that is, print the signature

The block signature information is similar to the method signature information. It mainly shows the block return value, parameters, and types

2.3 _Block_copy analysis

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;

    if (aBlock->flags & BLOCK_NEEDS_FREE) {

        // latches on high// release

        latching_incr_int(&aBlock->flags);

        return aBlock;

    }

    else if (aBlock->flags & BLOCK_IS_GLOBAL) {

        return aBlock;// Global block returns directly

    }

    else {// stack stack (compile time)

        // Its a stack block. Make a copy.

        size_t size = Block_size(aBlock);

        struct Block_layout *result = (struct Block_layout *)malloc(size);// The heap area opens up space

        if(! result)return NULL;

        memmove(result, aBlock, size); // bitCopy first copies the stack to the heap

#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)

        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {

            uintptr_t oldDesc = ptrauth_blend_discriminator(

                    &aBlock->descriptor,

                    _Block_descriptor_ptrauth_discriminator);

            uintptr_t newDesc = ptrauth_blend_discriminator(

                    &result->descriptor,

                    _Block_descriptor_ptrauth_discriminator);


            result->descriptor =

                    ptrauth_auth_and_resign(aBlock->descriptor,

                                            ptrauth_key_asda, oldDesc,

                                            ptrauth_key_asda, newDesc);

        }

#endif

#endif

        // reset refcount

        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed

        result->flags |= BLOCK_NEEDS_FREE | 2// logical refcount 1

        _Block_call_copy_helper(result, aBlock);

        // Set isa last so memory analysis tools see a fully-initialized object.

        result->isa = _NSConcreteMallocBlock;// Set ISA to heap block

        returnresult; }}Copy the code
  • Block_copy is mainly used to addThe stack block is copied to the heap block.

1. Check whether the block is being released. If yes, release the block directly. 2. Check whether the block is a global block. 3. Calculate the size of block in stack area and create the same size of memory space in heap area. Copy the contents of block in stack area to heap area by memmove. 4. Set flags and set the block type _NSConcreteMallocBlock

2.4 _Block_object_assign analysis

blockCapturing external variables_Block_object_assign will be called to perform a copy operation based on the modification type of the variable. The modification type is as follows:Continue to look at_Block_object_assignThe source code

When the operation of capturing external variables is mainly based on the type and modification of variables to do the corresponding operation:

  1. BLOCK_FIELD_IS_OBJECT: Reference count for ordinary variables+ 1Operation.
  2. BLOCK_FIELD_IS_BLOCKVoid (^object)(void) = block_block_copyOperation.

3. BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:__weak __block … x; The above modified variable X performs the _Block_byref_copy operation

  • _Block_byref_copyRealize the source

  1. Process the captured objectBlock_byrefConvert and save a copy.
  2. If the external variable is not copied to the heap memory, allocate memory, proceedcopyIf yes, the system returns after processing.
  3. Among themcopy srctheforwardingThe Pointers all point to the same piece of memory, that’s why, right__blockThe modified object hasModify theReasons for ability
  • Use __block to decorate variables

When compiled with Clang, the modified variables generate the Block_byref structureTo sum up: external variables__blockDecorated holding process:_Block_copyCopy block from stack to heap ->_Block_byref_copyCopy block object to Block_byref structure ->_Block_objct_asign(Make a copy of the __block modified variable)

2.5 _Block_object_dispose analysis

Dispose is used to select the corresponding method through the modification of the block variable

It’s similar to copy

  • To view_Block_byref_release

3. Summary

1. A block is essentially a function, a structure, and an object. A block can be an anonymous function without a name. Block_layout contains ISA (block type, stack block or heap area), flags (some identification of block), reserved(reserved field),invoke (callback execution),desc(description information). If there are external variables in the block code block, the stack block is compiled, and the stack block is copied to the heap block by _Block_copy. 4. The block signature information is similar to the method signature information, mainly reflects the return value of the block. Parameters and types 5. When we use a __block modified external variable, we copy it into the _Block_byref structure using _Block_object_assign. 6. We release the external variables we hold when we destroy them.