1. The underlying structure of blocks

  • First give the conclusion:

The essence of a block is a structure, and the methods executed within the block are Pointers to member variables – anonymous functions within the structure

  • Verify:
// main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block1)(void) = ^ {NSLog(@"i'm a NSGlobalBlock block");
        };
        
        block1();
        NSLog(@ "% @", block1); 
        
    }
    
    return 0;
}
Copy the code

Compile main.m to get c++ source code:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
Copy the code

Get the source main.cpp:


/// Block structure
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  /// constructor
  __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; }};/// the method executed inside the block is now a function
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6f_2snw6yxn2sz630d3r61gc0700000gn_T_main_a2088f_mi_0);
        }

/// The implementation structure within the block
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

/// Block information
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)};



int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6f_2snw6yxn2sz630d3r61gc0700000gn_T_main_a2088f_mi_1, block1);
        
        
        /// simplify
        // create a block structure
        block1 = __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA);
        /// call an anonymous function
        block1->funcPtr(a); }return 0;
}
Copy the code

The first block contains the isa pointer and the anonymous function pointer, and the second block contains the basic information (size) of the block.

Therefore, we conclude that the block structure is basically as follows:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __block_info {
  size_t reserved;
  size_t Block_size;
};

typedef struct _SanjiBlock {
     struct __block_impl impl;
     struct __block_info info;
} *sanjiBlock;
Copy the code

We can override the above blocks with _SanjiBlock

// Call block directly
block1();

// call by getting a pointer to the function
sanjiBlock sanjiBlock = (__bridge void *)block1;
void (*p)(void) = sanjiBlock->impl.FuncPtr;
p();

// Print the same result:
2020- 12- 09 14:35:15.207888+0800 BlockDetail[39554:1284881] i'm a NSGlobalBlock block 2020-12-09 14:35:15.207928+0800 BlockDetail[39554:1284881] I 'm a NSGlobalBlock block

Copy the code

2. Capture (capture), and __block

  • Capture of local variables (auto.static)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        /// auto can be omitted
        auto int age1 = 10;
        void (^block1)(void) = ^ {NSLog(@"age1 is %d", age1);
        };
        age1 += 10;
        block1();
        
        static int age2 = 10;
        void (^block2)(void) = ^ {NSLog(@"age2 is %d", age2);
        };
        
        age2 += 10;
        block2();
        
    }
    
    return 0;
}
/// Print the result
age1 is 10
age2 is 20
Copy the code

Still compile main.m:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age1;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age1, int flags=0) : age1(_age1) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int *age2;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int *_age2, int flags=0) : age2(_age2) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

Block1 captures the value of age1, and block2 captures the address of age2 for a simple reason: Age1 is an auto local variable that is destroyed when it leaves scope, so block1 can only capture its value (before it is destroyed), whereas static age2 is an in-memory variable, and Block2 captures its address and always has access to the latest value.

  • BlockWill global variables be captured?

No, because the global variable is always in memory and can be accessed directly to get the latest value.

  • Therefore, the following conclusions can be drawn:
Variable types Whether it will be caught by a block access
Auto local variable will Value passed
Static local variable will Pointer passed
The global variable Don’t Direct access to the

3. Type of block

Block type The environment
NSGlobalBlock The auto variable is not accessed
NSStackBlock The auto variable is accessed
NSMallocBlock NSStackBlock call copy

Code :(in ARC environment)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block1)(void) = ^ {NSLog(@"i'm block1");
        };
        
        int age = 10;
        void (^block2)(void) = ^ {NSLog(@"age is %d", age);
        };
        
        void (^block3)(void) = [block2 copy];

        NSLog(@"\nblock1 is %@\nblock2 is %@\nblock3 is %@\n", [block1 class], [block2 class], [block3 class]);
    }
    
    return 0;
}

/// Print the result
block1 is __NSGlobalBlock__
block2 is __NSMallocBlock__
block3 is __NSMallocBlock__
Copy the code

ARC copies the block from the stack to the heap, and references the counter +1. If the block is on the stack, it will be released when it is out of scope. Calling blocks at this point produces unpredictable results, leaving it up to the programmer to manage memory in the heap.

Change the environment to MRC:

block1 is __NSGlobalBlock__
block2 is __NSStackBlock__
block3 is __NSMallocBlock__
Copy the code

In what other cases are blocks automatically copied to the heap?

  • blockWhen the method return value is returned
  • willblockAssigned to__strongPointer time
  • blockIt’s available as cocoa APIusingBlockWhen the parameters of the

4, __Block

  • __BlockUsed to modifyblockIt cannot be modified internallyautovariable
  • __BlockThe decorated variable is wrapped as an object

4. Block memory management

Let’s start with two pieces of code: ARC

@interface OnePerson(a)
@property(nonatomic.assign) int age;
@end

@implementation OnePerson

- (void)dealloc
{
    NSLog(@"OnePerson --- dealloc");
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        OnePerson *person = [[OnePerson alloc] init];
        void (^block)(void) = ^{
           person.age = 10;
         };
        
        NSLog(@ "% @", block);
        NSLog(@" Block is about to be destroyed"); 
    }
    
    return 0;
}

/// Print the result
2020- 12- 09 20:09:12.955196+0800 BlockDetail[55739:1517434] <__NSMallocBlock__: 0x10040c800>
2020- 12- 09 20:09:12.955546+0800 BlockDetail[55739:1517434] block is about to be destroyed2020- 12- 09 20:09:12.955596+0800 BlockDetail[55739:1517434] OnePerson --- dealloc

Copy the code

The block captured the auto person variable, so it should be NSStackBlock, but since the ARC environment automatically copied to the heap, the Person object was delayed until the block was freed. Why?

Peep bottom source code again:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  OnePerson *__strong person; /// strong reference !!!!!
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, OnePerson *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The person object is captured, referenced by the __strong modifier, visible, and the heap block is strongly referenced to !!!! for the captured object

Blocks in stack space do not strongly reference captured objects, and blocks in heap space do strongly reference captured objects.

Why don’t stack blocks capture variables by strong references? The block in the stack itself is in danger of being released at any time, so how can you force references to captured variables? That is, the stack block does not retain captured external variables