Xcrun-sdk iphoneOS clang-arch arm64-fobjc-arc-fobjc-runtime = ios-10.0.0-rewrite-objc main.m -o main.cpp To explore the underlying block

Writing:

fuckingclosuresyntax.com/

fuckingblocksyntax.com/

The principle of block

The essence is an object, and the member variables include the address of the function and the local variables used in the function

The underlying structure of a block

Through the source code source address

Descriptor is a pointer to type Block_descriptor_1 at the surface, but block has a different flag, The real memory that descriptor points to may contain Block_descriptor_1 \ Block_descriptor_2 \ Block_descriptor_3, with flag, The value Block_descriptor_1 \ Block_descriptor_2 \ Block_descriptor_3 can be obtained

// Values for Block_layout->flags to describe block objects enum { BLOCK_DEALLOCATING = (0x0001), // runtime BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 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 BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler }; #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 void (*copy)(void *dst, const void *src); void (*dispose)(const void *); }; #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 }; // block struct Block_layout { void *isa; volatile int32_t flags; // contains ref count int32_t reserved; void (*invoke)(void *, ...) ; struct Block_descriptor_1 *descriptor; Import variables};Copy the code

Check this out with Clang rewrite, and it’s pretty much the same thing

int main(int argc, const char * argv[]) {
    int age = 0;
    void(^block)(void) = ^{
        age;
    };
    block();
    return 0;
}
Copy the code
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; __main_block_impl_0(struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy age; } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; void(*block)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, age ); (__block_impl *)block->FuncPtr(block);Copy the code

Variable to capture

When you get a normal local variable, you do value capture, because that local variable, maybe by the time the block is called, it doesn’t exist anymore when you get a static local variable, you do address capture and when you get a global variable, you don’t do value capture

Nature of the self

OC

@interface WYPerson : NSObject
@end
@implementation WYPerson
- (void)test {}
@end
Copy the code

C++

static void _I_WYPerson_test(WYPerson * self, SEL _cmd) {}
Copy the code

So self is essentially a parameter, and objC_msgsend is also passing the message receiver and SEL, so self is a local variable, so block is also going to capture self

Block type

Because blocks have isa Pointers, they must be typed

There are three types

Block: global __NSGlobalBlock__

Heap block: __NSMallocBlock__

Stack block: __NSStackBlock__

How to distinguish between

If no local variables are captured, the block is __NSGlobalBlock__, with data segments (global extents).

If a local variable is captured, the block is __NSStackBlock__ and exists on the stack

The return value of copy to __NSStackBlock__ is __NSMallocBlock__, which exists in the heap

type The copy operation
__NSGlobalBlock__ No operation is performed
__NSMallocBlock__ Reference count + 1
__NSStackBlock__ The copy on the heap

In arc, __NSStackBlock__ is automatically copied, for example:

  1. Block as the return value
  2. Block is referenced by a strong pointer
  3. Blocks used in GCD

Tips, you can turn off Arc in Build Settings

The memory structure of a block changes when a variable of an object type is captured

Only __main_block_desc_0 will change, and corresponding to the source code, the real memory that descriptor points to will contain Block_descriptor_2

When capturing a variable of an object type

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakperson, (void*)src->weakperson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->weakperson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

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

When capturing a variable of a common type

static struct __main_block_desc_0 { 
    size_t reserved; 
    size_t Block_size; 
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};
Copy the code

Block copies the __main_block_copy_0 function, which calls the _Block_object_assign function for each captured object based on the attributes of the members of the captured object type (__weak, __strong). To determine whether to perform operations like retain

Block dealloc calls the __main_block_dispose_0 function, which calls the _Block_object_dispose function, which decorates the members of the captured object type (__weak, __strong). To decide whether to do something like Release

As a result, there is no memory management of captured object variables in stack blocks until they are copied to heap blocks (no operations like retain, release).

__block

Variables modified by __block are converted to a special structure that has a member variable whose value is the variable being modified

The source code

// 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
};

struct Block_byref {
    void * __ptrauth_objc_isa_pointer isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};
Copy the code

With a clang rewrite

When modifying ordinary variables

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
Copy the code

When modifying a variable of an object type (when the object variable is __weak, the structure WYPerson *__strong person; __strong becomes __weak)

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 / / * * * * * * *
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
  / / * * * * * * *
 WYPerson *__strong person; // The __strong modifier changes to __weak
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, * (void((* *)char*)src + 40), 131);
}

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

It should be noted that when capturing variables decorated with __block, no matter what type of variable, there will be copy and dispose function address member variables in the __main_block_DESc_0 structure of the block variable. The _Block_object_assign function is called with different arguments depending on whether the variable is decorated with __block

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person1, (void*)src->person1, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->person2, (void*)src->person2, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person1, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->person2, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
Copy the code

Note also that MRC does not retain or release member variables decorated with __block

with__blockWhy is the modifier variable designed this way,(person1->__forwarding->person1);

// oc
 MyBlock block = ^{
        person1;
    };

// c++
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_person1_0 *person1 = __cself->person1; // bound by ref

        (person1->__forwarding->person1);
        }
Copy the code
  1. with__blockThe modified variable, at compile time, is converted to a structure that is initialized and stored on the stack when the function runs, which is calledA
  2. blockWhen initializing, it willAIs passed into the constructor as a parameter,blockThere is a pointer type member variable to point toAThe address of the
  3. blockWhen you copy, it willblockThe contents of the structure,copyOn the heap, at the same time, it willAThe contents are also copied to the heap and willblockPoint to theAChanges the value of the member variable of the pointer to the stackAThe address of the
  4. If you want to change or access only the contents on the heap, you need to change the contents of A on the stack and A on the heapforwardingPointers to addresses on the heap and, when accessed, access__forwording

So the __forwarding pointer to a block’s __Block_byref_person_0 member refers to the address on the stack before the block is copied to the heap, and to the address on the heap after the block is copied to the heap

This is because you want changes to variables everywhere to affect the same block of memory

The source code

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) { // 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

Why add block to it__weakAdd to block__strong

  1. add__weakIs used to resolve circular references
  2. add__strongObject is used to avoid releasing captured objects during block execution, and to support the release of captured objects during block executionIn the block,Use Pointers to access member variables of captured objects

Block also has method implementation, how to obtain the corresponding IMP and method signature

The source code also has a function to obtain the method signature, similar to the following code

// Get the implementation of the block method
imp_implementationWithBlock

// Get the block method signature
typedef NS_OPTIONS(int, BlockFlags) {
    BlockFlagsHasCopyDisposeHelpers = (1 << 25),
    BlockFlagsHasSignature          = (1 << 30)};typedef struct _Block {
    __unused Class isa;
    BlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct_Block *block, ...) ;struct {
        unsigned long int reserved;
        unsigned long int size;
        // requires BlockFlagsHasCopyDisposeHelpers
        void (*copy) (void *dst, const void *src);
        void (*dispose)(const void *);
        // requires BlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *BlockRef;

static NSMethodSignature* typeSignatureForBlock(id block) {
    BlockRef layout = (__bridge void *)block;
    
    if (layout->flags & BlockFlagsHasSignature) {
        void *desc = layout->descriptor;
        desc += 2 * sizeof(unsigned long int);
        
        if (layout->flags & BlockFlagsHasCopyDisposeHelpers) {
            desc += 2 * sizeof(void *);
        }
        
        if (desc) {
            const char *signature = (*(const char **)desc);
            return [NSMethodSignaturesignatureWithObjCTypes:signature]; }}return nil;
}
Copy the code

Use the NSInvocation to invoke blocks

  1. Setting parameters starts at bit 1, and the system automatically passes the block object itself at bit 0
  2. Use invokeWithTarget: Pass in a block object
  3. If you do not know the return value and parameter type of a block, you can use the block’s NSMethodSignature to infer this
    int(^block)(int.int) = ^ (int a, int b) {
        NSLog(@"%d - %d", a, b);
        return a + b;
    };
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:typeSignatureForBlock(block)];
    int a = 5;
    int b = 10;
    [invocation setArgument:&a atIndex:1];
    [invocation setArgument:&b atIndex:2];
    [invocation invokeWithTarget:block];
    int res = 0;
    [invocation getReturnValue:&res];
    NSLog(@"%d", res);
Copy the code