Block nature

  • A block is also essentially an OC object with an ISA pointer inside it
  • A block is an OC object that encapsulates a function call and its environment

Define a block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
    
        void(^block)(void) = ^(void){
            NSLog(@"hello-world");
        };
        block();
    }
    return 0;
}

Copy the code

To see the underlying implementation of the block, run the xcrun-sdk iphoneOS clang-arch arm64-rewrite-objc main.m command line to get the main. CPP file

The main function code in main. CPP is as follows

int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; / / define the block variable void block (*) (void) = ((void (*) ()) & __main_block_impl_0 (__main_block_func_0 (void *), &__main_block_desc_0_DATA)); / / implementation code within the block (void (__block_impl *) (*)) ((__block_impl *) block) - > FuncPtr) ((__block_impl *) block); } return 0; }Copy the code

__main_block_impl_0 is the internal code of the __main_block_func_0 block that encapsulates the memory occupied by the block defined by the function __main_block_desc_0_DATA

The __main_block_IMPL_0 structure is as follows

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; // Block type impl.Flags = Flags; impl.FuncPtr = fp; // execute function address Desc = Desc; // Store __main_block_desc_0 (0, sizeof(__main_block_impl_0)))}}; struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; // the pointer points to the address of the function executing inside the block};Copy the code

__main_block_func_0

Static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString) *)&__NSConstantStringImpl__var_folders_2v_yzvmxk793t31txq41bn50r1r0000gn_T_main_f08b1f_mi_0); }Copy the code

__main_block_desc_0_DATA

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

Eventually the block is converted to a __main_block_IMPL_0 structure object, which is assigned to the variable block, The passing arguments are __main_block_func_0 and __main_block_desc_0_DATA to execute the __main_block_IMPL_0 constructor, The __main_block_desc_0_DATA function is assigned to __main_block_IMPL_0 ->FuncPtr, and the executive function is block->FuncPtr(block), Before deleting redundant code is ((void (__block_impl *) (*)) ((__block_impl *) block) – > FuncPtr) ((__block_impl *) block); So why can block be cast directly to __block_impl? Since the first line of the __main_block_IMPL_0 structure is __block_impl, the memory address of __main_block_IMPL_0 is the same as the memory address of __block_IMPl, forcing the conversion will not be a problem.

Block variable capture

  • To ensure that external variables can be accessed within a block, blocks have a variable capture mechanism

auto

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        int age = 10;
        void (^block)(void) = ^(void){
            NSLog(@"age is %d",age);
        };
        age = 20;
        block();
    }
    return 0;
}
Copy the code

The main CPP code

int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); age = 20; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

You can see that age is passed directly into the block’s constructor as a value pass.

static

        int age = 10;
        static int height = 20;
        void (^block)(void) = ^(void){
            NSLog(@"age is %d height is %d",age,height);
        };
        age = 20;
        height = 30;
        block();
Copy the code

main.cpp

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        static int height = 20;
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
        age = 20;
        height = 30;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
Copy the code

The __main_block_IMPL_0 structure also starts with *, so the height value changes.

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; int *height; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The global variable

Global variables are accessed directly without being captured in the __main_block_IMPL_0 structure.

__block qualifier

  • __bllock can be used to solve the problem that the auto variable cannot be modified inside a block
  • __block cannot modify global or static variables.
  • The compiler wraps a __block variable into an object

code

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        __block int age = 10;
        void(^block)(void) = ^(void){
            age = 20;
            NSLog(@"age is %d",age);
        };
        block();
     }
    return 0;
}

Copy the code

main.cpp

struct __Block_byref_age_0 { void *__isa; __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }Copy the code

There is an age pointer to an __Block_byref_age_0 structure inside the block. We find that an age of type int is inside the structure.

That is, the __block variable is wrapped as an object by the compiler, and our member variable is placed inside the object.

If we look inside this __Block_byref_age_0, one of these variables that might be confusing is this __forwarding. It is a pointer to the structure itself. __forwarding->age (age->__forwarding->age)

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*);
Copy the code

There are also two more Pointers in the __main_block_desc_0 structure, which are memory management-related functions.