1. The nature of blocks

A block is essentially an OC object with an ISA pointer inside it, which encapsulates the function call address as well as the function call environment (function parameters, return values, captured external variables, and so on). When we define a block, what does the underlying storage structure look like after it’s compiled?

In this example, we define a block, and use the variable age outside the block. The underlying storage structure of the block is shown in the following figure. The underlying structure of the block is __main_block_IMPL_0.

  • impl->isa: is an ISA pointer, so it is an OC object.
  • impl->FuncPtrA pointer to a function is a pointer to a function that encapsulates the code to be executed in a block and points to that function.
  • Desc->Block_size: Memory size occupied by blocks.
  • age: Captured external variable age, visible block will capture the external variable and store it in the block’s underlying structure.

When we call a block(block()), we find the enclosed function using the FuncPtr pointer and pass the address of the block as an argument to the function. We pass blocks to functions because some of the data that the function needs to execute exists in the block’s structure (such as captured external variables). If a block is defined with arguments, the block is called by passing the address of the block along with its arguments to the encapsulated function.

2. Block’s variable capture mechanism

Variables outside the block can be captured by the block, so that variables outside the block can be used inside the block. Different types of variables have different capture mechanisms. Let’s look at an example:

int c = 1000; Static int d = 10000; Int main(int argc, const char * argv[]) {@autoreleasepool {int a = 10; Static int b = 100; // Static local variable void (^block)(void) = ^{NSLog(@"a = %d",a);
             NSLog(@"b = %d",b);
             NSLog(@"c = %d",c);
             NSLog(@"d = %d",d);
         };
         a = 20;
         b = 200;
         c = 2000;
         d = 20000;
         block();
    }
    return0; } / / * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * 2020-01-07 15:08:37. 541840 + 0800 CommandLine [70672-7611766] a = 10, 2020-01-07 15:08:37.542168+0800 CommandLine[70672:7611766] B = 200 2020-01-07 15:08:37.542201+0800 CommandLine[70672:7611766] C = 2000 2020-01-07 15:08:37.542222+0800 CommandLine[70672:7611766] D = 20000Copy the code

We’ll talk about that later, but let’s look at what the underlying storage structure of this block looks like after it’s compiled. To convert the main.m file into a compiled C/C ++ file, run xcrun-sdk iphoneOS clang-arch arm64-rewrite-objc main.m from the command line. The block structure is then found by searching __main_block_IMPL_0 in the file.

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *b;
};
Copy the code

We can see that I defined four variables and only two local variables were captured, and the two local variables were captured differently. Why is that? Here are the explanations:

2.1 Capture of global variables

Block does not capture either ordinary or static global variables. Because global variables can be accessed anywhere, they can be accessed directly from within the block without capturing them, so when the value of a global variable is changed externally, the last value is printed inside the block.

2.2 Static local variable capture

We find that the static local variable b defined by the block is captured as int *b in the block structure; In other words, the block is actually the address of the variable B that is captured. Inside the block, the address of B is used to obtain or modify the value of B. Therefore, changing the value of B outside the block affects the value of B inside the block, and changing the value of B inside the block also affects the value of B outside the block. So it’s going to print b = 200.

2.3 Capture of common local variables

Ordinary local variables are defined in a function or block of code like int a = 10; Int a = 10, so it’s also called auto. Unlike static local variables, ordinary local variables are captured by a block and then stored in the block’s underlying structure as int A; In other words, the block captures the value of A (i.e., 10) and redefines a variable inside the block to store that value. A outside the block and a inside the block are two different variables, so changing the value of A outside the block does not affect the value of A inside the block. So it prints a = 10.

So one might wonder, why do ordinary local variables capture values, just like static local variables capture addresses? No, I can’t. If we call the block outside the braces, calling the value of a via a pointer inside the block will throw an exception because a has already been freed. The lifetime of a static local variable is the same as the lifetime of the entire program, that is, b is not released during the entire program, so this situation does not occur.

If a static local variable is never released, why should a block catch it? This is because static local variables are only scoped to the bracketed class. Outside of the bracketed class, it still exists but cannot be accessed from outside. As we’ve already seen, the code inside a block is wrapped in a function, which must be outside the braces of b, so the function cannot access B directly, so the block must capture it.

2.4 Block capture variable summary

  • Global variables – no capture, direct access.
  • Static local variable – captures variable address.
  • Ordinary local variable – is the value of the captured variable.

So the key to determining whether a variable is going to get caught by a block is whether it’s a local variable or a global variable. Let’s see if a block captures self in one of the following cases:

- (void)blockTest{
    
    // 第一种
    void (^block1)(void) = ^{
        NSLog(@"%p",self); }; Void (^block2)(void) = ^{self.name = @"Jack"; }; Void (^block3)(void) = ^{NSLog(@)"% @",_name); }; Void (^block4)(void) = ^{[self name]; }; }Copy the code

To figure this out, we’re basically trying to figure out whether self is a local variable or a global variable. A lot of people think it’s a global variable, but it’s actually a local variable. So again, self is a local variable. So one wonders, why is it a local variable? Where is it defined. To understand this, we need to have some understanding of OC’s method invocation mechanism.

An object in OC calls a method, which essentially sends a message to this object. For example, if I call [self blockTest], it gets converted to C and becomes objc_msgSend(self, @selector(blockTest)). OC’s blockTest method has no arguments, but when converted to objc_msgSend, it has two extra arguments: self, which points to the caller, and the other argument, which is the method to call. So for all OC methods, they have these two default arguments, the first argument is self, so self is just passed in as an argument, it’s really a local variable.

With this problem solved, the above cases are simple, and each of the four cases is where the block captures self. The first one, the second one, the fourth one is easy to understand, but the third one might be a little bit confusing, why would a block capture self when there’s no self in it? _name is not written without self, it just leaves out self, it’s the same as self->_name, so be careful.

3. Block type

If a block is an OC object, then it has a class. What is its class? We can get the block’s class by calling the block’s class method or object_getClass() function.

- (void)test{
    int age = 10;
    
    void (^block1)(void) = ^{
        NSLog(@"-- -- -- -- --");
    };
    NSLog(@"Block1 classes: %@",[block1 class]);
    
    NSLog(@"Block2 classes: %@",[^{
        NSLog(@"----%d",age);
    } class]);
    
   NSLog(@"Block3 classes: %@",[[^{
       NSLog(@"----%d",age); } copy] class]); } / / * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * 2020-01-08 09:07:46. 253895 + 0800 AppTest [72445-7921459] block1 class: __NSGlobalBlock__ 2020-01-08 09:07:46.254027+0800 AppTest[72445:7921459] Block2 class: __NSStackBlock__ 2020-01-08 09:07:46.254145+0800 AppTest[72445:7921459] Block3 class: __NSMallocBlock__Copy the code

There are three classes of block, as printed above: __NSGlobalBlock__, __NSStackBlock__, and __NSMallocBlock__. One might wonder if printing blocks 2 and 3 does not define a block1 and then print it like block1. This is because in ARC mode, if a block of type __NSStackBlock__ is pointed at by a strong pointer, the system will automatically copy the block to __NSMallocBlock__, which will affect the result of the run.

Here are the differences between the three types:

3.1 __NSGlobalBlock__

If a block does not access ordinary local variables (that is, it does not access any external variables or access static local variables or global variables), then the block is __NSGlobalBlock__. Blocks of type __NSGlobalBlock__ have data areas in memory (also known as global or static areas, where global and static variables are stored). A block of type __NSGlobalBlock__ calling copy does nothing.

Let’s look at the inheritance chain of __NSGlobalBlock__ :

- (void)test{
    void (^block)(void) = ^{
        NSLog(@"-- -- -- -- --");
    };
    NSLog(@"% @" -- -,[block class]);
    NSLog(@"% @" -- -,[[block class] superclass]);
    NSLog(@"% @" -- -,[[[block class] superclass] superclass]);
    NSLog(@"% @" -- -,[[[[block class] superclass] superclass] superclass]); } / / * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * 2020-01-08 11:03:34. 331652 + 0800 AppTest - __NSGlobalBlock__ [72667-7957820] 2020-01-08 11:03:34.331777+0800 AppTest[72667:7957820] -- __NSGlobalBlock 2020-01-08 11:03:34.331883+0800 AppTest[72667:7957820] -- NSBlock 2020-01-08 11:03:34.331950+0800 AppTest[72667:7957820] -- NSObjectCopy the code

See that blocks also inherit from NSObject, __NSGlobalBlock__’s inheritance chain is: __NSGlobalBlock__ : __NSGlobalBlock: NSBlock: NSObject.

3.2 __NSStackBlock__

If a block has access to a normal local variable, it is an __NSStackBlock__, which is stored in memory in the stack area. The stack area is not controlled by the developer, but is managed by the system, so it is important to call __NSStackBlock__. Make sure it hasn’t been released yet. If you copy a block of type __NSStackBlock__, it copies the block from the stack to the heap.

The inheritance chain of __NSStackBlock__ is: __NSStackBlock__ : __NSStackBlock: NSBlock: NSObject.

3.3 __NSMallocBlock__

An __NSStackBlock__ block calls copy, which copies the block from the stack to the heap. The block on the heap is __NSMallocBlock__, so __NSMallocBlock__ blocks are stored in the heap. If you copy a block of type __NSMallocBlock__, the block’s reference count is +1.

The inheritance chain for __NSMallocBlock__ is: __NSMallocBlock__ : __NSMallocBlock: NSBlock: NSObject.

In an ARC environment, the compiler automatically copies blocks on the stack to the heap, depending on the situation. Stack blocks can be copied onto the heap in four ways:

A. block as a function return value:

typedef void (^MyBlock)(void);

- (MyBlock)createBlock{
    int a = 10;
    return ^{
        NSLog(@"******%d",a);
    };
}
Copy the code

B. Assign block to strong pointer:

Assigning a block on the defined stack to the strong pointer myBlock becomes a heap block.

- (void)test{ int a = 10; // local variable void (^myBlock)(void) = ^{NSLog(@"a = %d",a);
    };
    block();
}
Copy the code

C. When block is passed to Cocoa API as an argument:

Such as:

[UIView animateWithDuration: 1.0 f animations: ^ {}].Copy the code

D. block as a parameter to the GCD API:

Such as:

dispatch_async(dispatch_get_main_queue(), ^{
        
});
Copy the code

In addition, in the MRC environment, it is recommended to use the copy keyword to define block attributes, which will copy blocks from the stack to the heap.

@property (copy,nonatomic) void(^block)void;
Copy the code

In ARC, defining a block attribute with the copy or strong keyword copies the stack block to the heap, so either way works.

@property (strong, nonatomic) void (^block)(void); 
@property (copy, nonatomic) void (^block)(void);
Copy the code

4. Block captures local variables of object type

A block captures object types differently from primitives. Variables of object types involve strong references and weak references. How are strong references and weak references handled under blocks?

If the block is on the stack, there is no strong reference to the object inside the block, regardless of whether the object being captured is strong or weak. So let’s focus on the block on the heap.

Let’s first look at how strongly referenced objects are stored in the underlying structure once they are captured by a block. Here we use the following command to convert OC code to C/C ++ code:

Xcrun-sdk iphoneOS clang-arch arm64-rewrite-fobjc-arc-fobjc-Runtime =ios-8.0.0 main.mCopy the code
// OC code int main(int argc, const char * argv[]) {@autoreleasepool {Person * Person = [[Person alloc] init]; person.age = 20; void (^block)(void) = ^{ NSLog(@"age--- %ld",person.age);
         };
        block();

    }
    return0; } struct __main_block_impl_0 {struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__strong person; };Copy the code

As you can see, in contrast to the basic data type, the Person object is captured by a block and has a modifier __strong in its structure.

Let’s look at what weak reference objects look like when they are captured:

// OC code int main(int argc, const char * argv[]) {@autoreleasepool {Person * Person = [[Person alloc] init]; person.age = 20; __weak Person *weakPerson = person; void (^block)(void) = ^{ NSLog(@"age--- %ld",weakPerson.age);
         };
        block();

    }
    return0; } struct __main_block_impl_0 {struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__weak weakPerson; };Copy the code

It can be seen that the key word of weakPerson in the block becomes __weak.

The key word used to modify the captured object type variable in a block is __strong, __weak, and __unsafe_unretained. So what does this result keyword do?

The copy function is called when a block is copied to the heap. The copy function calls the _Block_object_assign function internally. The _Block_object_assign function operates on these three keys.

  • If the key word is__strongAnd then inside the block, the object will be executed onceretainOperation, reference count +1, which means the block will forcibly reference this object. For this reason, it is easy to create circular references when using blocks.
  • If the key word is__weakor__unsafe_unretainedThat block is a weak reference to the object and does not cause a circular reference. So we usually define one outside the block__weakor__unsafe_unretainedDecorated with a weak pointer to an object, which is then used inside a block to solve the problem of circular references.

When a block is removed from the heap, its internal dispose function is called, which calls the _Block_object_dispose function to automatically release strongly referenced variables.

4. The __block modifier

Before we get to __block, let’s look at the following code:

- (void)test{
    int age = 10;
    void (^block)(void) = ^{
        age = 20;
    };
}
Copy the code

What’s wrong with this code? The compiler reports an error and cannot change the age value in a block. Why is that?

Because the age is a local variable, its scope and life cycle is only in the test method, and also introduced the front, will block the bottom braces in the code in a function, also is the equivalent of now is to visit the test method in another function of the local variable, this is certainly not enough, so he complains.

What do I do if I want to change the age value in a block? We can define age as a static local variable static int age = 10; . A static local variable has the same scope as the test method, but its life cycle is the same as that of the program, and the block captures the static local variable by capturing the address of the age, so the block changes the value of the age through the address of the age, so there is no problem.

This is not recommended because static local variables are not released during program execution, so use them sparingly. What other ways are there to implement this requirement? This is the __block keyword.

- (void)test1{
    __block int age = 10;
    void (^block)(void) = ^{
        age = 20;
    };
    block();
    NSLog(@"%d",age);
}
Copy the code

When we use the __block keyword, what does the underlying layer do to allow us to access age inside the block? Let’s take a look at what the structure of the block looks like when the above code is converted to c++ code.

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref }; struct __Block_byref_age_0 { void *__isa; // isa pointer __Block_byref_age_0 *__forwarding; // If the block is on the heap then the pointer points to itself, if the block is on the stack then the pointer points to the block int __flags after it is copied onto the heap; int __size; // Struct size int age; // The age actually captured};Copy the code

We can see that age with __block is changed to __Block_byref_age_0 *age in the block structure; , and __Block_byref_age_0 is a structure with a member int age; This is the real captured external variable age, and actually the address of the external variable age also points to here, so when you change the age either outside of the block or inside the block, you actually change the age by going to this address.

So age with __block is no longer a local variable inside a test1 method. Instead, it is wrapped into an object in which age is stored. Wrapped as an object because the first member of the __Block_byref_age_0 structure is the ISA pointer.

__block modifies memory management of variables:

__block either decorates the underlying datatype or the object datatype, the underlying structure wraps it as an object (I’ll call it __blockObj) with a pointer to __blockObj in the block structure. Since it’s an object, how does a block manage its memory internally?

When a block is on the stack, there is no strong reference to __blockObj inside the block.

When a block calls copy from the stack to the heap, it copies __blockObj to the heap and makes a strong reference to __blockObj.

When a block is removed from the heap, its internal dispose function is called, which in turn calls the _Block_object_dispose function to release __blockObj.