From our analysis of the block’s nature in the previous article, we can see that the block’s nature is an OC object with an ISA pointer, so the block must have its own type. Isa points to an &_nsconcretestackblock in C++ code. In this article, we will continue to share the types and inheritance chains of blocks.

Inheritance relationships

There are three types of blocks, which can be obtained by isa or by calling the class method, all of which ultimately inherit from NSObject. Let’s get the block type from class

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

Copy the code

The result is as follows, which further confirms that the block is an OC object.

block[12750:370967] __NSGlobalBlock__
block[12750:370967] __NSGlobalBlock
block[12750:370967] NSBlock
block[12750:370967] NSObject
Copy the code

Block type

So we figured out that blocks inherit from NS Blocks, and eventually from NSObject, so let’s look at the three types of blocks

  • NSGlobalBlock
  • NSMallocBlock
  • NSStackBlock still gets the block type by class
Void (^block1)(void) void (^block1)(void) = ^{NSLog(@"this is a block"); }; // global static: __NSGlobalBlock__ 0x1 int age = 10; void (^block2)(void) = ^{ NSLog(@"this is a block %d",age); }; //__NSMallocBlock__ heap block 0x6 starts with NSLog(@"%@ - %@ - %@",[block1 class],[block2 class],[^{
            NSLog(@"this is a block %d",age); //__NSStackBlock__ stack block 0x7} class]);Copy the code

Output: __NSGlobalBlock__ — __NSMallocBlock__ — __NSStackBlock__ but we still need to edit it into C++ code and look at the type isa points to. Are _NSConcreteStackBlock;

It is important to note that the results of the clang command will be different from those of the original code and should be used as a reference.

So why are the above three blocks of these three types, let’s continue to analyze the memory distribution.

Distribution of memory

A picture is worth a foreword

  • The code you write is in the program area

  • Global variables are usually placed in the data area

  • The memory of the program area and the data area is handled automatically by the compiler

  • Heap: Dynamically allocating memory such as [NSObject alloc], malloc(20) requires the developer to both allocate memory and manage memory themselves

  • Stack: local variables, function parameters, etc., the system automatically allocates memory, automatically release memory

  • NSGlobalBlock is in the data area, NSMallocBlock is in the heap area, and NSStackBlock is in the stack area

So a lot of blocks that we write in development, how do we know what type of block it is?

Here are some rules for judging:

  1. NSGlobalBlock: if the auto variable is not accessed, it is an NSGlobalBlock. Copy an NSGlobalBlock is still an NSGlobalBlock
  2. NSStackBlock: Access to the auto variable is NSStackBlock (need to be tested in MRC environment because the ARC environment compiler did the copy for us when printing)
  3. NSMallocBlock: NSStackBlock that does a copy is NSMallocBlock

Copy is important because calling copy on a block on the stack copies a copy from the stack to the heap, which increases the reference count by one, while NSGlobalBlock calling copy on a data block does nothing.

Block of copy

NSStackBlock does a copy of a block on the stack to the heap. In ARC, the system automatically copies a block on the stack to the heap. Here are some examples

  1. Block is the return value of the function
typedef void (^MyBlock)(void);
MyBlock returnBlock() { int age = 20; // Copy is automatically calledreturn ^{
        NSLog(@"----- %d",age);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyBlock block = returnBlock();
        block();
        NSLog(@"% @",[block class]); //NSMallocBlock }return 0;
}
Copy the code

According to the result of our analysis above, the auto variable is called, which is an NSStackBlock, but the final result is an NSMallocBlock, which means that the compiler did a copy operation for us. Of course, under ARC, we don’t need to care about release. The compiler also did release for us.

  1. Block assigns a value to the __strong pointer
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 20;
        MyBlock block = ^{
            NSLog(@"----- %d",age);
        };
        block();
        NSLog(@"% @",[^{
            NSLog(@"----- %d",age); } class]); //NSStackBlock NSLog(@"% @",[block class]); //NSMallocBlock
    }
    return 0;
}
Copy the code
  1. API Chinese method name contains usingBlock method
 NSArray *array = [NSArray array];
        [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
        }];
Copy the code
  1. The GCD of block
static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            <#code to be executed once#>
        });
Copy the code

In essence, blocks on the stack do not retain external variables (MRC) or strongly reference external variables (ARC). Only blocks on the heap can hold variables.

__block qualifier

The __block modifier is used many times in development, so let’s start with why it is used and what happens when __block is added to it.

Why __block? One of the most common uses of a block is to modify a local variable outside of the block. As we’ve already seen, the auto variable is passed by value, is recorded in the structure of the block, and is accessed across functions. How can we modify the auto variable in a function that executes a block in another function? Of course you can’t. We can change the value of the auto variable by modifying it with __block, so we can think about what __block does. Maybe he just changed the value pass to address pass?

Modifying the Auto variable

To modify the auto variable, let’s start with a method that doesn’t use __block

  1. The static modifierIf a static modifier is captured by a block, it is an address pass. If a static modifier is captured by a block, it is an address pass. If a static modifier is captured by a block, it is an address pass
  2. The global variableNot to mention, anyone can change it without using a block to capture it
  3. __blockIn addition to the above two solutions (which we generally don’t use because there may be memory problems), __block is our best solution
__block principle

Or through clang compiled into C++ code area analysis of the principle of __block, first write a section of test code

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 20;
        MyBlock block = ^{
            age = 33;
            NSLog(@"----- %d",age);
        };
        block();
    }
    return 0;
}
Copy the code

__block int age = 20; The code was eventually compiled

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
Copy the code

Age with the __block modifier is compiled into a __Block_byref_age_0 object

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

__forwarding is assigned &age, which is the address of the __Block_byref_age_0 object itself, and the value of age (20) is also stored in this object. Age ->__forwarding->age

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

            (age->__forwarding->age) = 33;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_76267f_mi_0,(age->__forwarding->age));
        }
Copy the code

By now it should be clear why a __block variable can be modified in a block. In fact, it is also a disguised implementation of the address, and only when the address is obtained can it change the data in the memory to which the corresponding address refers.

__forwarding

(age->__forwarding->age) = 33; Why should there be a “forwarding” in the structure? __forwarding refers to itself.

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
Copy the code

Here we can see that __forwarding is assigned &age, which is its own memory address, but does the __forwarding pointer always point to itself? Let’s do an experiment.

__block int I = 0; NSLog(@"Outside the Block % p",&i);
    
    void (^myBlock)(void) = [^{
        i ++;
        NSLog(@"Inside the Block % p",&i);
    }copy];
Copy the code

Copy the Block to the heap, at which point the printed addresses of the two I variables are different.

Block outside 0x7ffF5FBff818 Block inside 0x1002038A8Copy the code

A different address is an obvious indication that the __forwarding pointer does not point to its previous self. So where does the __forwarding pointer point to now? Let’s look at the picture and talk

This should make it clear that __forwarding exists so that blocks can continue to refer to __block variables on the heap after they are copied from the stack to the heap.

There is a small problem with using arrays in blocks, and it is also confusing to understand:

addObject
array
nil

Nil = nil = nil = nil = nil = nil = nil = nil

Now, why doesn’t addObject get an error, because addObject doesn’t actually change the value of array, it just manipulates array, whereas block just can’t change the auto variable, it doesn’t manipulate the variable, so it doesn’t get an error.