The essence of the Block object

The essence of the Block object

What is a block

The bottom layer of the block is an OC object, which encapsulates function calls and the calling environment, and has an isa pointer inside

Source code

^{
    int a = 10;
    a = 20;
};
 

After oc code is converted to c++ code, it is roughly like this

//block ,  isa 
struct __block_impl {
    //isa block oc 
  void *isa;
  int Flags;
  int Reserved;
    
    // 
  void *FuncPtr;
};

//block 
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, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// block 
void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = 10;
}

// block 
struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

//Block 
&__main_block_impl_0(
     //block __main_block_func_0 
     __main_block_func_0,

     //__main_block_desc_0 ,  
     &__main_block_desc_0_DATA)
);
 

The structure is shown in the figure

Block variable capture mechanism

auto modified local variables

Captured inside the block, value transfer, the value of the captured variable cannot be modified

Source code

//auto 
int a = 10;
        
void (^block)(void) = ^{

    NSLog(@"%d", a);
};
block();

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 10;
        
        void (^block)(void) = ^{
            NSLog(@"%d", a);
        };
        block();
    }
    return 0;
}
 

c++ code

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;

        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);
    }
    return 0;
}
 

From line 4, we can see that a variable with the same name as the external name will be added inside the block, and the value outside the block will be assigned to this variable, so it will give us an illusion of accessing external variables inside the block

static modified local variables

Capture the inside of the block, transfer the address, and modify the corresponding memory data through this address

Source code

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int a = 10;
        
        void (^block)(void) = ^{
            
            NSLog(@"%d", a);
        };
        block();
    }
    return 0;
}
 

c++ code

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        static int a = 10;

        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);
    }
    return 0;
}
 

It can be seen from the int *a on the fourth line and the &a on the 18th line that when the block captures the static modified variable, it captures the address value of the external variable a through &a and assigns it to its own int *a internal variable.

Global variable

No capture, direct access

Source code

int a = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void (^block)(void) = ^{
            NSLog(@"%d", a);
        };
        block();
    }
    return 0;
}
 

c++ code

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, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
 

From the C++ implementation, I did not find any information about storing external variables. It can be seen that the block does not capture access to global variables and directly accesses them.

Type of block

To use the following code in the MRC environment, the block on our stack will be copied to the heap in the ARC environment

__NSGlobalBlock__

//__NSGlobalBlock__:  auto 
void (^block)(void) = ^{
    NSLog(@"global block");
};
NSLog(@"%@", [block class]);
 

__NSStackBlock__

//__NSStackBlock__:  auto 
int a = 10;
void (^block2)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", [block2 class]);
 

__NSMallocBlock__

//__NSMallocBlock__: __NSStackBlock__ copy
int b = 10;
void (^block3)(void) = [^{
	NSLog(@"%d", b);
} copy];
NSLog(@"%@", [block3 class]);
 

Note: When we want to hold the block object, we must use the copy attribute, so that we can ensure that the block is not destroyed when we make it

__block modifier

__block int a = 10;
void (^block)(void) = ^{
    // ,  __block 
    a = 20;
    NSLog(@"%d", a);
};
block();
 

When a variable of a basic data type is modified by __block, it will be captured as an object inside the block

c++ code

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __Block_byref_a_0 a = {0, &a, 0, sizeof(__Block_byref_a_0), 10};
        void (*block)(void) = (
           &__main_block_impl_0(
                __main_block_func_0,
                &__main_block_desc_0_DATA,
                &a,
                570425344
            )
        );
        block->FuncPtr(block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_bs_8j14my3x2yxb7f_65thjrd1m0000gn_T_main_9aad1a_mii_1, (a.__forwarding->a));
    }
    return 0;
}
 

When a variable of a basic type is captured by the block, it is no longer the simple a before, a becomes complicated, and it becomes an object type

__weak modifier

The problem of circular references, when the code is executed, the p object will not be released, causing memory leaks

Person * p = [Person new];
void (^block)(void) = ^{
    NSLog(@"block   ------ %p", p);
};
p.block = block;
block();
NSLog(@"block   ------");
 

Use __weak to solve the problem of circular references

Person * p = [Person new];
__weak typeof(p) weakP = p;

void (^block)(void) = ^{
    NSLog(@"block   ------ %p", weakP);
};
p.block = block;
block();
NSLog(@"block   ------");
 

p object is released normally

__strong modifier

If you want to hold the captured object in the block before the code is executed, use this keyword to modify

Person * p = [Person new];
__weak typeof(p) weakP = p;

void (^block)(void) = ^{
    __strong typeof(weakP) strongP = weakP;
    NSLog(@"block   ------ %p", strongP);
};
p.block = block;
block();
NSLog(@"block   ------");
 

This blog is purely my own study notes. I hope to have a summary result of my study. If there is any mistake, I welcome everyone to correct me, my brother is very grateful!