Block clang analysis

Compile the following code into CPP with the command clang-rewrite-objc main.m -o main.cpp

Block underlying structure

After compiling to CPP, find the corresponding main method, and then find the corresponding block code as follows:

int a = 18;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
Copy the code

This code looks very confusing, let’s remove the corresponding type to simplify:

  • __main_block_impl_0Method implementation
  • block->FuncPtr(block)call

The underlying block is a structure __main_block_IMPL_0, which then inherits __block_impl, which is defined as follows

struct __block_impl {
  void *isa; // Point to the region
  int Flags; / / tag
  int Reserved;
  void *FuncPtr; // Call the method
};
Copy the code

__main_block_impl_0 (implementation)

  • through__main_block_impl_0Initialize the assignment block
  • __main_block_func_0The block itself is passed in, and then the block code is implemented.

__block decorates external variables

If we want to modify an external variable inside a block, we need to modify it with __block, so what does __block do? Modify the external variable with __block, and then clang again.

Find variables with__Block_byref_a_0The structure wraps it and passes it to the block.__Block_byref_a_0: Internal definition

Forwarding actually passes in the address of the external variable.

And then we’re also accessing the address of A in the calling function, so the variables that we’re changing in the block are actually external variables.

block copy

Place the breakpoint where the block is declared and then use the assembly and symbol breakpoint objc_retainBlock->_Block_copy. _Block_copy is in libsystem_blocks. Dylib. Since this library is not open source, you can use libclosure instead.

  • The essence of a block is a Block_layout
  • If it is a global block, return directly
  • If it is a stack block, external variables are captured

in_Block_copyBreak point, passregister read x0What type is a block when it’s passed in at the very beginningThen print the return type on return:

The X0 registers are then printed separatelySummary: Stack blocks are used at compile time. If external variables are captured, they are copied from the heap to become heap blocks.

Block_layout structure

struct Block_layout {

    void *isa;  // Specifies the block type
    volatile int32_t flags; // Id, including whether there is a signature, destructing
    int32_t reserved; // Reserve the field
    BlockInvokeFunction invoke;// Function, i.e. FuncPtr
    struct Block_descriptor_1 *descriptor; // Additional information
    // imported variables
};

#define BLOCK_DESCRIPTOR_1 1

**struct** Block_descriptor_1 {

    uintptr_t reserved;

    uintptr_t size;

};

\


#define BLOCK_DESCRIPTOR_2 1

struct Block_descriptor_2 {

    // requires BLOCK_HAS_COPY_DISPOSE

    BlockCopyFunction copy;

    BlockDisposeFunction dispose;

};

\
#define BLOCK_DESCRIPTOR_3 1

struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
 const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT

};
Copy the code

Where Block_descriptor_2 and Block_descriptor_3 are optional fields. Global search get method:

hereBlock_descriptor_2.Block_descriptor_3Is in theBlock_descriptor_1On the basis of memory translation processing.

Blocks capture the variable life cycle

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc ViewController.m -o ViewController.cpp Clang analysis is done through the command stack block above, which captures the objdect type without the __block modifier.

  • By description field__ViewController__viewDidLoad_block_desc_0, this is actually the corresponding source code insideBlock_descriptor_1,Block_descriptor_2,Block_descriptor_3.
  • Look at the copy method__ViewController__viewDidLoad_block_copy_0Found that the final call_Block_object_assign

_Block_object_assign analysis

The source code does different things for different types

  • BLOCK_FIELD_IS_OBJECT, if the object type is assigned directly
*dest = object;
Copy the code
  • BLOCK_FIELD_IS_BLOCK, called if a block type is captured_Block_copycopy
  • BLOCK_FIELD_IS_BYREF, called if the __block modifier is used_Block_byref_copy.
  • All other cases are direct assignments

_Block_byref_copy analysis

The object passed in wraps a Block_byref object, the original object.

struct Block_byref *src = (struct Block_byref *)arg;
Copy the code

Respace and generate a DST object

struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
Copy the code

Set forwarding to ensure that both inside and outside a block point to the same object

copy->forwarding = copy; // 
src->forwarding = copy;  //
Copy the code

Byref_keep and byref_keep processing

copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;

// External variables are captured - memory processing - life cycle saving
 (*src2->byref_keep)(copy, src);
Copy the code

But what does this save do, can not be found in the source code, we use __block decoration, and then regenerate. CPP file, open

  • The packing ofBlock_byrefThere will be_Block_byref_id_object_copyand__Block_byref_id_object_dispose

Discovery is called again_Block_object_assignCopy. Call:_Block_object_disposeThe release of

Summary: So when using a variable decorated with __block, there will be a second copy. Plus the block gets copied from stack to heap three times.