I’m sure you’re all familiar with blocks, but do you really know how they work? Why __block, for example, and what is the use of the modifier? What are the consequences of not adding? How blocks are implemented and so on… This article will reveal the implementation principle of Block

The topic

So just to give you a question, let’s think about what happens if we call these two methods separately.

void blockFunc1()
{
    int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}Copy the code
void blockFunc2()
{
    __block int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}Copy the code

The answer is

blockFunc1 : num equal 100
blockFunc2 : num equal 200Copy the code

Did someone get that wrong? Two more functions. The result for both is the same as for blockFunc2, which prints num 200

Int num = 100; void blockFunc3() { void (^block)() = ^{ NSLog(@"num equal %d", num); }; num = 200; block(); }Copy the code
void blockFunc4()
{
    static int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}Copy the code

Question: If num is a local variable with the _ _block modifier, if num is a global variable, and if num is a static local variable with the _ _block modifier, the block output is the same. Num, which is a local variable and has no __block attached, outputs the same value in the block as before it was assigned. Why is that? To explore this question we need to look at how the underlying structure is implemented, right

Explore the inner workings

Objective-c is a fully dynamic language, and everything is implemented based on the Runtime! Create a Command Line Tool project and place the above code in main.m, as shown in the figure below





main.m

Here we will open the terminal, CD to the project directory, and rewrite OC to C with the following command

clang -rewrite-objc main.mCopy the code





rewrite-objc

At this point we can see that there is an extra main. CPP file in the current directory, open it and roll to the bottom





Open the main CPP




main.cpp

Here we can see the C implementation of blockFunc1

void blockFunc1()
{
    int num = 100;
    void (*block)() = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}Copy the code

Get rid of the cast

void blockFunc1() { int num = 100; / / * * * * * * * * * * * * * * * * * * * * * * * * * the key words of * * * * * * * * * * * * * * * * * * * * * * * void () = (* block) &__blockFunc1_block_impl_0(__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num)); // ***************************************************** num = 200; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }Copy the code

And we can see that here

A block is actually a pointer to a structure

The structure is





__blockFunc1_block_impl_0

Let’s look at blockFunc2 with __block





blockFunc2

In blockFunc1, the block refers to a structure called blockFunc1_block_impl_0 and is initialized with three parameters (the last flags of blockFunc1_block_impl_0 have default parameters, so you don’t need to pass them). The third argument is the num, which, in contrast to blockFunc2, does not have an * sign, so it only passes values rather than addresses, whereas num = 200; There’s not much use for eggs. This is why blockFunc2, blockFunc3, and blockFunc4 print the changed value of num, but blockFunc1 does not.





Here we can also see:

The compiler generates the corresponding function from the block’s internal code

SO

In summary, a block is internally used as a pointer to a structure. When a block is called, it actually finds the corresponding function according to the corresponding pointer of the block, and then calls it and passes in itself

The realization of the __block

Let’s look at _block. _block is also converted to a structure with five variables

struct __Block_byref_num_0 { void *__isa; // isa pointer __Block_byref_num_0 *__forwarding; // The instance itself int __flags; int __size; int num; // our num value};Copy the code





The image corresponds to blockFunc2

__block int num = 100;Copy the code

When num is created and decorated with __block, these five variables are initialized when we execute

num = 200;Copy the code

Corresponds to the

(num.__forwarding->num) = 200;Copy the code

__forwarding is the instance itself, that is, the &num of __Block_byref_num_0. Find the num variable and change it from 100 to 200

This is the end of the disclosure of the internal implementation of Block. Hopefully this article will give you a deeper understanding of Block. Thank you for your patience!