What is a Block

  • A Block is an object that encapsulates a function and its execution context

Let’s take a look at the essence of a Block from the source code

  • We write three lines of code in a method. The first line defines a local variable, the second line is a Block, and the third line is a call to the Block

Here we use a clang compiler command clang-rewrite-objc xxx.m to see the source code implementation

  • So when we write that piece of code in the compiler, the first line I is an instance method followed by the object and method name, and we pass in two arguments self and a selector factor

  • Then the first line of code in our method does not change after compilation, so let’s focus on the compiled changes to the Block method

  • First we can look at a structure called __BlockOneObj__testMethod_block_impl_0, in which we pass several parameters, The first argument (void*)__BlockOneObj__testMethod_block_func_0 we know by name that this is an untyped function pointer, The second argument &__blockoneobj__testmethod_block_desC_0_data is a structure that describes the Block and then takes the address, and the third argument muIntNum is the local variable that we define. Finally, take the structure address cast and assign it to the Block we defined

Then let’s see what happens in the __BlockOneObj__testMethod_block_impl_0 structure, as shown in the following figure

What is the data structure inside the first structure

There is also a function below the structure described above, as shown in the figure below

So what is a call to a Block

Block call is actually a function call, we can see from the source

  • The Block is first cast (__block_impl *)Block

  • FuncPtr (FuncPtr) : FuncPtr (FuncPtr) : FuncPtr (FuncPtr) : FuncPtr (FuncPtr) : FuncPtr (FuncPtr) : FuncPtr (FuncPtr) : FuncPtr It then goes back to the __BlockOneObj__testMethod_block_func_0 function, and finally makes the call

Block intercept variable

Let’s start with a piece of code

- (void)testMethod {
    
    int muIntNum = 6;
    int(^Block)(int) = ^int(int num){
        return num *muIntNum;
    };
    
    muIntNum = 4;
    Block(2);
}

Copy the code

What is the value that this code returns after executing Block(2)? ——- the answer is 12 so let’s see why 12 and what is the nature of the Block intercept variable

  • Intercepts values for local variables of primitive data types

  • Local variables for object types are intercepted along with their ownership modifier

  • Local static variables are intercepted in the form of Pointers

  • Global variables and static global variables are not intercepted

So let’s go straight to the code

#import "BlockTwoObj.h"Int global_var = 4; Static int static_global_var = 5; static int static_global_var = 5; @implementation BlockTwoObj - (void)testMethodTwo {// Local variable int var = 1; __unsafe_unretained id unsafe_obj = nil. __strong id strong_obj = nil; Static int static_var = 3; void(^Block)(void) = ^{ NSLog(@"Base datatype local variable: %d", var);
        NSLog(@"Object type local (__unsafe_unretained modifier) : %@", unsafe_obj);
        NSLog(@"Object type local variable (__strong modifier) : %@", strong_obj);
        
        NSLog(@"Local static variable: %d", static_var);
        
        NSLog(@"Global variable: %d", global_var);
        NSLog(@"Global static variable: %d", static_global_var);
    };
    
    Block();
}

@end
Copy the code

Clang – rewrite-objc-fobjc-arc xxx.m clang – rewrite-objc-fobjc-arc xxx.m

  • In this figure, we can clearly see the interception of variables in the Block. It is important to note that the interception of local static variables is a pointer, which means that if the local static variable is modified, the latest value is used in the Block

__block qualifier

When do we use __block modifiers? In general, assignment to a intercepted variable requires adding a __block modifier, but assignment does not mean use, remember!!

For example, whether the __block modifier is needed in the code below

NSMutableArray *muArr = [[NSMutableArray alloc] init]; Void (^Block)(void) = ^{// This is an addition, not an assignment, so there is no need to modify __block [muArr addObject:@"111"];
    };
    
    Block();
Copy the code

What about the following code snippet?

__block NSMutableArray *muArrOther = nil; Void (^BlockOther)(void) = ^{muArrOther = [NSMutableArray array]; muArrOther = [NSMutableArray array]; }; BlockOther();Copy the code

When assigning a value to a variable

  • The __block modifier is needed to modify local variables (both primitive data types and object types)

  • Static local variables, global variables, and static global variables do not need __block modifiers, because global and static global variables do not involve capturing variables, and static local variables do not need to be modified by using Pointers to manipulate the corresponding variables

Let’s take a look at the code that we used above

- (void)testMethod {
    
    __block int muIntNum = 6;
    int(^Block)(int) = ^int(int num){
        return num *muIntNum;
    };
    
    muIntNum = 4;
    Block(2);
}
Copy the code

Now the Block returns 8. Why is that? We’re just using __block

  • Because here a very strange change happens, __block modified variables become objects

See the flow chart below

  • First __block int muIntNum is converted to the first such structure with an ISA pointer, which we can also understand as an object

  • From this point of view, muIntNum is compiled to become an object, which is found through the __forwarding pointer and then assigned

  • In the __block variable, there is a pointer to __forwarding, which points to itself. It is important to note that if you are on the stack, if you are on the heap, the __forwarding pointer will not point to itself, as we will see below

  • So when we change the value of this variable on the stack, we find ourselves using the __forwarding pointer to change the value of this variable

So the question here is what is the use of the fact that __forwarding refers to itself on the stack? We can change this completely by accessing the member variable. Why do we need this pointer? Read on

Block memory management

There are three types of blocks

  • _NSConcreteGlobalBlock global Block

  • _NSConcreteStackBlock stack Block

  • _NSConcreteMallocBlock heap Block

Block Copy operation

  • When a Block on the stack is copied to the heap to create an identical Block, with the same Block and __block variable, the Block object on the stack is destroyed when the variable ends, and the Block on the heap remains, so if the Block on the stack is not copied to the heap, Crashes after destruction because the Block object cannot be found

  • Of course, we have a question here, if MRC environment, if copy operation on the stack, will cause memory leak, the answer is yes, equivalent to an object alloc, but not the corresponding relese operation

  • When a Block on the stack is copied, an identical Block is created on the heap. The __forwarding pointer in the Block on the stack points to the Block’s __block variable on the heap. And the Block’s __forwarding pointer on the heap also points to its own __block variable

Reference books

Objective-c advanced programming: Multithreading and memory management for iOS and OS X

Github

Demo