Block portal 🦋🦋🦋

Exploring the nature of blocks (1) — Basic knowledge

Explore the essence of blocks (2) — the underlying structure

Explore the essence of blocks (3) — basic type of variable capture

Explore the nature of blocks (5) — variable capture of object types

Exploring the nature of blocks (6) — An in-depth analysis of __blocks

In the previous section, we learned that a Block is also an OC object because it also has an ISA pointer in its underlying structure. For example, the following block:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        The definition of / / Block
        void (^block)(void) = ^ () {NSLog(@"Hello World");
        };
        
        NSLog(@ "% @", [block class]);
        NSLog(@ "% @", [block superclass]);
        NSLog(@ "% @", [[block superclass] superclass]);
        NSLog(@ "% @", [[[block superclass] superclass] superclass]);
    }
    return 0; } * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * * * *2019- 06- 05 14:44:53.179548+0800 Interview03-block[16670:1570945] __NSGlobalBlock__
2019- 06- 05 14:44:53.179745+0800 Interview03-block[16670:1570945] __NSGlobalBlock
2019- 06- 05 14:44:53.179757+0800 Interview03-block[16670:1570945] NSBlock
2019- 06- 05 14:44:53.179767+0800 Interview03-block[16670:1570945] NSObject
Program ended with exit code: 0
Copy the code

In the above code, we print the block type and the superclass type using the [XXX class] and [XXX supperClass] methods. You can see that the inheritance relationship looks like __NSGlobalBlock__->__NSGlobalBlock->NSBlock->NSObject which is also a good proof that a block is an object, because its base class is NSObject. And we know that the ISA member variable in the block must inherit from NSObject.

Its compiled form is as followsThe information in the figure indicates that the blockisaThe class pointing to is_NSConcreteStackBlock. Shouldn’t the class isa points to be the same as the class printed when the program runs?

Here’s one more detail: Currently, LLVM compiler to generate the intermediate file is no longer the c + + form, and we are in the command line, is actually a through c + + files generated by the clang, both on grammatical details is distinct, but most of the logic and principle or similar, so by clang generated between c + + code, only for us as a reference, In the end, the result must be the result of the Runtime, because the Runtime will still process and adjust the intermediate code compiled before the program runs.

The type of the Block

There are three types of blocksHere we come to one by one analysis, first we review the memory layout of the program

  • Code blocks take up very little space and are generally stored in a low address space in memory, where all the code we write is stored
  • The data segment is used to store global variables
  • The heap is dynamically allocated to hold objects generated by alloc in our code. Dynamically allocated memory requires the programmer to allocate and manage memory. For example, objects generated by ALLOc in OC need to be released by calling RELEas (MRC), and objects generated by MALloc in C must be released by free().
  • The stack area system automatically allocates and destroys memory for local variables generated within the function

Let’s use a classic example to see where different types of blocks are stored!

(1) NSGlobalBlock(that is, _NSConcreteGlobalBlock)

If a block does not use/access an auto variable internally, it is of type __NSGlobalBlock__ and is stored in the data segment of the application

Let’s verify that with codeThe above three figures show that several other variables except the auto variable are accessed by the block, and the printed results are as follows

2019- 06- 05 16:38:31.885797+0800 Interview03-block[17590:1712446] __NSGlobalBlock__
Program ended with exit code: 0
Copy the code

The result shows that blocks are of type __NSGlobalBlock__. In fact, this type of block does not have many application scenarios, so it is rarely seen, just for understanding.

(2) NSStaticBlock(that is, _NSConcreteStaticBlock)

If a block uses/accesses an auto variable (the auto variable), it is of type __NSStaticBlock__ and stored in the application stack

As we continue to verify the wave, the previous code adjustments are as followsThe print result is as follows

2019- 06- 05 16:45:25.990687+0800 Interview03-block[17648:1721701] __NSMallocBlock__
Program ended with exit code: 0
Copy the code

Yi? Why is the result here__NSMallocBlock__? Should not be__NSStaticBlock__? The reason is that the current isARCEnvironment,ARCThe mechanics have already done some work for us, but to see what it is, let’s turn it offARCRunning through the code again, the output looks like this

2019- 06- 05 16:52:08.500787+0800 Interview03-block[17712:1730384] __NSStackBlock__
Program ended with exit code: 0
Copy the code

Well, we see that, without the help of ARC, the block type here is indeed __NSStackBlock__. In fact, we’re going to use this type of block in a lot of situations, because a lot of times, we’re going to use environment variables in blocks, and most of the environment variables are going to be auto variables, so think about it, if we don’t do anything, what’s going to happen? (💡 reminder: combined with the life cycle of stack area contents)

Let’s adjust the raw code as follows

#import <Foundation/Foundation.h>

void (^block)(void);// Global variable block

void test(){
    int a = 10;
    
    block =     ^(){
                    NSLog(@" the value of a is --%d",a);
                };
    
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}
Copy the code

Based on the above code, what would you expect to print? Would the value of A, 10, be printed correctly? Look at the results

2019- 06- 05 17:04:25.915160+0800 Interview03-block[17820:1746272The value of a is --- 272632584.
Program ended with exit code: 0
Copy the code

Look, the value of A is 272632584, and obviously, if we use this value in our program, we will definitely break our original design idea.

So let’s analyze it:

  • The code,blockIs a global variable defined outside of a function
  • In the functiontest()Within the code^(){NSLog(@"a = --%d",a); };It’s going to generate one for us first__NSStaticBlock__The type ofBlock, it stores with the current functiontest()And then its pointer is assigned to the global variableblock.
  • inmainFunction, the function is called firsttest(), global variablesblock Just point to thetest()This one on the stack__NSStaticBlock__The type ofBlockAnd thentest()The call ends and the stack space is reclaimed
  • thenblockThat’s where the problem is, at this point,test()The stack space is being reclaimed by the system to do other things, that is, the top one__NSStaticBlock__The type ofBlockMemory is also reclaimed. Although throughObject block,(or,Pointer to the block), and finally access the original variableaIs pointing to the block of memory, but the value of this inch is not guaranteed to be what we need10So you can see that the print result is an unexpected number.

❓❓ So how to solve this problem? It’s natural to think that we need that__NSStaticBlock__The type ofBlockIt is moved to the heap so that it is not destroyed when the function stack area is reclaimed, but can be destroyed after the programmer has finished using it.

(3) NSMallocBlock(that is, _NSConcreteMallocBlock)

__NSMallocBlock__ is converted to __NSMallocBlock__ by calling the copy method, which is stored on the heap

Adjust the above code as follows

#import <Foundation/Foundation.h>

void (^block)(void);// Global variable block

void test(){
    int a = 10;
    
    block =     [^(){ NSLog(@" the value of a is --%d",a); } copy];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        test();
        block();
        NSLog(@" Block is of type %@",[block class]);
    }
    return 0;
}
Copy the code

Before assigning a block value, copy is performed. The following output is displayed

2019- 06- 05 17:44:16.940492+0800 Interview03-block[18166:1799723The value of a is --- 10
2019- 06- 05 17:44:16.940752+0800 Interview03-block[18166:1799723] Block is of type __NSMallocBlock__ Program ended withexit code: 0
Copy the code

As you can see, the print value of variable A is still 10, and the block does point to an __NSMallocBlock__. [^(){NSLog(@”a = –%d”,a);} copy]; The returned Block is stored on the heap, so the value of a in it is the same as when it was captured, 10, so the print is not affected.

You might be wondering, what if you call the copy method on __NSGlobalBlock__? The result is still an __NSGlobalBlock__. If you are interested, you can code it yourself.

conclusion

Calling copy on each type of block results in the following

Block copy problem in ARC environment

In the above section, we discussed the storage of blocks in memory based on the MRC environment. Since the blocks we generate in normal code are created within a function, that is, they are of type __NSStaticBlock__, and we usually need to save them and call them at some point in the future, but at that point the function stack on which the block is located no longer exists, so under MRC, We need to copy the contents of __NSStaticBlock__ to the heap memory by calling copy to make it a __NSMallocBlock__, so that it doesn’t affect future use, and as a consumer, we need to make sure that after using the block, we don’t need it again. Call the release method on the block to release it to avoid memory leaks.

ARC has done a lot of tedious and meticulous work for us developers, so that we do not have to spend too much energy on memory management, including block copy processing. For example, let’s tweak the previous code to remove the copy operation, as follows

#import <Foundation/Foundation.h>

void (^block)(void);// Global variable block

void test(){
    int a = 10;
    
    block =     ^(){ NSLog(@" the value of a is --%d",a);   };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        test();
        block();
        NSLog(@" Block is of type %@",[block class]);
    }
    return 0;
}
Copy the code

Turn the ARC switch on and run the program and we get the following result

2019- 06- 05 20:29:31.503282+0800 Interview03-block[19472:1922021] * * * * * * * * * * * *10
2019- 06- 05 20:29:31.503652+0800 Interview03-block[19472:1922021] Block is of type __NSMallocBlock__ Program ended withexit code: 0
Copy the code

As you can see, this is the same as if we had copied the block manually under MRC, indicating that ARC did the copy for us.

In an ARC environment, the compiler automatically copies blocks on the stack to the heap as necessary, as in the following case

  • Block is returned as a function argument
  • Assign a block to__strongPointer time
  • Block asCocoa APIThe Chinese name containsusingBlockMethod parameter
  • Block as a method parameter of the GCD API

Small details – how Block attributes are written

  • Suggestions for writing Block attributes under MRC

@property (nonatomic, copy) void(^block)(void);

  • ARC Block attribute writing suggestions

@property (nonatomic, copy) void(^block)(void); // recommend @property (nonatomic, strong) void(^block)(void);

The ARC keyword copy and strong have the same effect on the block attribute, because ARC automatically copies the block when the __strong pointer points to it, but it is recommended to use the copy keyword for code consistency.

Block portal 🦋🦋🦋

Exploring the nature of blocks (1) — Basic knowledge

Explore the essence of blocks (2) — the underlying structure

Explore the essence of blocks (3) — basic type of variable capture

Explore the nature of blocks (5) — variable capture of object types

Exploring the nature of blocks (6) — An in-depth analysis of __blocks