preface

This article will cover block types, circular references, and related interview questions, and the next article will verify and analyze these upper-level representations using the underlying source code.

To prepare

  • Libclosure source

I. Clang analysis block

Clang analysis of basic variables

Create mMain in main.m:

int mMain(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        return mMain();
    }
    return NSApplicationMain(argc, argv);
}

int mMain(void) {
    int a = 18;
    void(^block)(void) = ^{
        printf("ssl - %d",a);
    };
    block();
    
    return 0;
}
Copy the code

To get the main. CPP file, run clang-rewrite-objc main.m -o main. CPP.

int mMain(void) { int a = 18; void(*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, a)); block->FuncPtr(block); return 0; } struct __mMain_block_impl_0 { struct __block_impl impl; struct __mMain_block_desc_0* Desc; int a; __mMain_block_impl_0(void *fp, struct __mMain_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • inmMainFunction is called__mMain_block_impl_0.
  • __mMain_block_impl_0It’s a structure. There’s oneint aA member variable has a constructor wherea(_a)Is theaA member variable has been assigned.
  • impl.isa = &_NSConcreteStackBlock;And you can see here is aStack blockAnd in our understanding, this is supposed to beHeap blockThat’s right. We’re going to analyze it.
  • fpismMainIn the__mMain_block_func_0And assigns it toFuncPtrAnd the finalblockThe call to is the callFuncPtrThat is__mMain_block_func_0.

Check the __mMain_block_func_0:

static void __mMain_block_func_0(struct __mMain_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    printf("ssl - %d",a);
}
Copy the code
  • __cselfis__mMain_block_impl_0Structure.
  • Here,aAnd the outsideaIs not a value, so changes will report errors.

__block modifies the basic variable clang analysis

Modify the code to modify a with __block:

int mMain(void) {
    __block int a = 18;
    void(^block)(void) = ^{
        printf("ssl - %d",a);
    };
    block();
    
    return 0;
}
Copy the code

CPP: clang-rewrite-objc main.m -o main. CPP: clang-rewrite-objc main.m -o main. CPP

int mMain(void) { __Block_byref_a_0 a = { (void*)0, (__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18}; Void (*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); block->FuncPtr(block); return 0; } struct __mMain_block_impl_0 { struct __block_impl impl; struct __mMain_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __mMain_block_impl_0(void *fp, struct __mMain_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; }}; struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; static void __mMain_block_func_0(struct __mMain_block_impl_0 *__cself) { __Block_byref_a_0 *a = __cself->a; // bound by ref printf("ssl - %d",(a->__forwarding->a)); }Copy the code
  • You can see the variablesaIt’s compiled here__Block_byref_a_0Structure, when initialized will&aAssigned to__forwarding, which assigns its own address to itself__forwarding.
  • call__mMain_block_impl_0When the constructor of the__forwardingAssigned to__mMain_block_impl_0Member variable ofa.
  • So call__mMain_block_func_0Function,__cself->aAnd outside theaIs pointing to the same memory address, can be modified.

Block assembly analysis signature, copy process

Assembly analysis yields the underlying calls and libraries

Create an NSObject object and print it in the block, using assembly debugging to find the function objc_retainBlock that the block first calls:

Objc_retainBlock symbol breakpoint to get the next function called _Block_copy:

_Block_copy symbol breakpoint to get libsystem_blocks. Dylib:

_Block_copy is available in libsystem_blocks, but the libsystem_blocks library is not open source. You can disassemble the source code and use libclosure 79 as an alternative project.

_Block_copy source code analysis

Enter the _Block_copy function:

// Copy, or bump refcount, of a block. If really copying, call the copy helper if present. 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 latching_incr_int(&aBlock->flags); return aBlock; Else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock; } else {// Its a stack block. Make a copy. Size_t size = Block_size(aBlock); Struct Block_layout *result = (struct Block_layout *)malloc(size); if (! result) return NULL; // 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) 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); Copy of descriptor Result -> Descriptor = ptrAuth_auth_and_resume (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); // isa marks MallocBlock result->isa = _NSConcreteMallocBlock; return result; }}Copy the code
  • blockIs the nature ofBlock_layoutStructure.
  • The function is passing youStack blockWill open up a new oneHeap blockAnd thenStack blockThe associated values are copied to the heap.

Check the Block_layout:

struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // Where to point block, stack/heap/global volatile int32_t flags; // Contains ref count whether it is destructing and whether it has a signature int32_t reserved; BlockInvokeFunction invoke; Struct Block_descriptor_1 *descriptor; // Imported variables};Copy the code

Check flags values:

// Values for Block_layout->flags to describe block objects enum { BLOCK_DEALLOCATING = (0x0001), // Whether the Runtime is destructing BLOCK_REFCOUNT_MASK = (0xfffe), // Runtime reference count mask 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), BLOCK_HAS_COPY_DISPOSE = (1 << 25), // Compiler whether copy, BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code BLOCK_IS_GC = (1 << 27), // runtime BLOCK_IS_GLOBAL = (1 << 28), Block BLOCK_USE_STRET = (1 << 29), // Compiler: undefined if! BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // Compiler};Copy the code

Assembler analysis signature, copy process

_Block_copy symbol breakpoint, print block through register:

  • And you can see that at this pointblockorStackBlock.

Assembler follows the flow, goes to the bottom, prints the block again through the register:

  • You can seeStackBlockTurned out to beMallocBlock.blockThe address is different.
  • signature:v8@? 0The return value isvoid, accounting for8Bytes,@?Type, from0Start at position #).
  • invoke: function caller,copy,disposeThe following will be analyzed.

Get signature details from NSMethodSignature:

(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] <NSMethodSignature: 0xb91f68C7625b0cf7 > Number of arguments = 1 frame size = 224 is struct return? NO // NO return value: -------- -------- -------- -------- type encoding (v) 'v' flags {} modifiers {} frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0} memory {offset = 0, size = 0} argument 0: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / parameters starting from 0 position type encoding (@) '@? '// The type is @? Flags {isObject, isBlock}} frame {offset = 0, offset adjust = 0, size = 8 Memory {offset = 0, size = 8} (LLDB) memory {offset = 0, size = 8}Copy the code

Three, Block_layout structure

To viewBlock_layoutAnd the relateddescriptorStructure:

struct Block_layout { void * __ptrauth_objc_isa_pointer isa; // Where to point block, stack/heap/global volatile int32_t flags; // Contains ref count whether it is destructing and whether it has a signature int32_t reserved; BlockInvokeFunction invoke; Struct Block_descriptor_1 *descriptor; // 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
  • Here,Block_descriptor_2,Block_descriptor_3It’s optional, it may or may not, and then it goes through themgetFunction to analyze.

By searching the query, find_Block_descriptor_2,_Block_descriptor_3Function:

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);
    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);
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}
Copy the code
  • Block_descriptor_2Is through theblocktranslationBlock_descriptor_1The size of the.
  • If you havecopy,disposeThe function,blockTranslation firstBlock_descriptor_1Is the size ofBlock_descriptor_2.Block_descriptor_2And then I shift it by its sizeBlock_descriptor_3.
  • If there is nocopy,disposeThe function,Block_descriptor_3byblocktranslationBlock_descriptor_1The magnitude of theta is directly obtained.

Back to assembly debugging, LLDB print validation

  • 0x00000001048f0010isBlock_descriptor_2The address of its first32byte0x00000001048ef3e3isBlock_descriptor_3The first member variable ofsignature, prints out its valuev8@? 0.

4. The capture variable life cycle of __block

Block prints an __block modified NSObject object inside a block:

int mMain(void) {
    __block NSObject *obj = [NSObject new];
    void(^block)(void) = ^{
        NSLog(@"ssl - %@",obj);
    };
    block();
    
    return 0;
}
Copy the code

Clang-rewrite-objc main.m -o main. CPP

int mMain(void) { __Block_byref_obj_0 obj = { (void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend((id)objc_getClass("NSObject"), sel_registerName("new")) }; void(*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344)); block->FuncPtr(block); return 0; } struct __mMain_block_impl_0 { struct __block_impl impl; struct __mMain_block_desc_0* Desc; __Block_byref_obj_0 *obj; // by ref __mMain_block_impl_0(void *fp, struct __mMain_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; }}; struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __Block_byref_obj_0 { void *__isa; __Block_byref_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSObject *obj; }; static void __mMain_block_func_0(struct __mMain_block_impl_0 *__cself) { __Block_byref_obj_0 *obj = __cself->obj; // bound by ref NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_rpsdqw797gn7n3l8myk82s5w0000gn_T_main_35fc7e_mi_0,(obj->__forwarding->obj)); }Copy the code

Capture variable Block_byref copy analysis

View the __mMain_block_desc_0 related functions:

static struct __mMain_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __mMain_block_impl_0*, struct __mMain_block_impl_0*);
  void (*dispose)(struct __mMain_block_impl_0*);
} __mMain_block_desc_0_DATA = { 0,
                                sizeof(struct __mMain_block_impl_0),
                                __mMain_block_copy_0,
                                __mMain_block_dispose_0};

static void __mMain_block_copy_0(struct __mMain_block_impl_0*dst, struct __mMain_block_impl_0*src) {
    _Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __mMain_block_dispose_0(struct __mMain_block_impl_0*src) {
    _Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}
Copy the code
  • copyanddisposeThat’s what we mentioned aboveBlock_descriptor_2The member variable in, where assigned to__mMain_block_copy_0Functions and__mMain_block_dispose_0The function,__mMain_block_copy_0Call the_Block_object_assignFunction.
  • _Block_object_assignThe first argument to&dst->objIs in the heap__Block_byref_obj_0.
  • _Block_object_assignThe second argument tosrc->objIn the stack__Block_byref_obj_0.
  • _Block_object_assignThe third pass to the function is8That isBLOCK_FIELD_IS_BYREFType.
    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

Search the _Block_object_assign function in the source code:

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)) {// Common object types case BLOCK_FIELD_IS_OBJECT: /******* id object = ... ; [^{ object; } copy]; ********/ // There is no operation and the system level arc is handling _Block_retain_object_default = fn (arc) _Block_retain_object(object); // The stack block object is assigned to the stack block object *dest = object; break; / / __block modification type case BLOCK_FIELD_IS_BLOCK: / * * * * * * * void ^ (object) (void) =... ; [^{ object; } copy]; ********/ *dest = _Block_copy(object); break; case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: case BLOCK_FIELD_IS_BYREF: // 8 /******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; * * * * * * * * / / / the object here is __Block_byref_obj_0 * dest = _Block_byref_copy (object); break; case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: /******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/ *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: /******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; ********/ *dest = object; break; default: break; }}Copy the code
  • flagsisBLOCK_FIELD_IS_BYREFType, so the call is_Block_byref_copyFunction, passed inobjectI already said it’s on the stack__Block_byref_obj_0.

Enter the _Block_byref_copy:

Static struct Block_byref *_Block_byref_copy(const void *arg) {struct Block_byref * SRC = (struct Block_byref *arg) Block_byref *)arg; If ((SRC ->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) { 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; } // Capture external variables - memory processing - life cycle save (*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
  • Open up again on the heapBlock_byrefStructure memory will be on the stackBlock_byrefThe values are copied over, and the values on the stack and heapforwardingThey all point to this memory space.
  • Called after the copy operation is completebyref_keepFunction, what does this function call.

Object copy analysis in Block_byref

Block_byref:

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 { // requires BLOCK_BYREF_HAS_COPY_DISPOSE BlockByrefKeepFunction byref_keep; // 8 BlockByrefDestroyFunction byref_destroy; / / 8}; struct Block_byref_3 { // requires BLOCK_BYREF_LAYOUT_EXTENDED const char *layout; };Copy the code

Size: byref_keep: byref_keep: byref_keep

int mMain(void) {
    __Block_byref_obj_0 obj = {
        (void*)0,(__Block_byref_obj_0 *)&obj,
        33554432,
        sizeof(__Block_byref_obj_0),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        objc_msgSend((id)objc_getClass("NSObject"), sel_registerName("new"))
    };
    void(*block)(void) = __mMain_block_impl_0(__mMain_block_func_0, &__mMain_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
    block->FuncPtr(block);

    return 0;
}

struct __Block_byref_obj_0 {
    void *__isa;
    __Block_byref_obj_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    NSObject *obj;
};
Copy the code

Byref_keep calls __Block_byref_id_object_copy_131:

// block -> Block_byref -> object
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
Copy the code
  • In combination with__Block_byref_obj_0The structure andBlock_byrefStructure, shift it down40Student: 1 byte, so you get 1 byteNSObject *objforobjThat’s called_Block_object_assignFunction, passed inflagsis131.

  • 3+128=131, so whenflagsis131When do is*dest = objectOperation, which is in the heapobjectAnd the stackobjectPointing to the same memory space.

Block copy summary:

  • blockthrough_Block_copyFunctions are copied from the stack to the heap.
  • blockCapture variableBlock_byrefThrough the_Block_object_assignFunctions are copied from the stack to the heap.
  • Block_byrefIn theobjectthrough_Block_object_assignFunction to make related copies.

The release of

The __Block_byref_id_object_dispose_131 function called on release:

static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
Copy the code

Call the _Block_object_dispose function

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