This chapter explains blocks. I can’t remember how many times I watched it the first time because the author had converted objective-C code into C++ code.

This summary not only contains the contents of this book, but also some relevant knowledge of Block I read in other blogs and added my own understanding. Moreover, the structure of the article is not quite consistent with the original book, which is rearranged by me.

Let’s look at the structure of this article (Blocks) :

Need to know first

Objective-c to C++

Since you need to see the C++ source code for the Block operation, so you need to know the method of conversion, take a look for yourself:

  1. Write the code in the OC source file block.m.
  2. Open the terminal and CD to the folder where block.m is located.
  3. The inputclang -rewrite-objc block.m, the corresponding block. CPP file is automatically generated in the current folder.

About the characteristics of several variables

Possible variables in c functions:

  • Parameters of a function
  • Automatic variable (local variable)
  • Static variables (static local variables)
  • Static global variable
  • The global variable

Moreover, due to the special storage area, three of these variables can be called at any time in any state:

  • A static variable
  • Static global variable
  • The global variable

The other two, however, have their own corresponding scope, beyond the scope, will be destroyed.

Well, with those two points in mind, it’s a little easier to understand what follows.

The essence of the Block

Conclusion: A Block is essentially an Objective-C object implementation of a closure, and in a nutshell, a Block is an object.

The following are analyzed from the surface to the bottom:

Superficial analysis of the essence of a Block: It is a type

A Block is a type that generates a value that can be assigned to a Block variable. Here’s an example:

int (^blk)(int) = ^ (int count){
        return count + 1;
};
Copy the code
  • The code to the left of the equals sign represents the type of the Block: it takes an int and returns an int.
  • The code to the right of the equals sign is the value of this Block: it is an implementation of the Block type defined on the left side of the equals sign.

If we often use a block of the same type in a project, we can use a typedef to abstract this type of block:

typedef int(^AddOneBlock)(int count);

AddOneBlock block = ^(int count){
        return count + 1;// The implementation code
};
Copy the code

This makes it relatively easy to assign and pass blocks because the block’s type has been abstracted.

Deep dive into the essence of a Block: It’s an Objective-C object

A Block is really an Objective-C object because it has an ISA pointer in its structure.

So let’s convert objective-C code to C++ code to see how blocks are implemented.

OC code:

int main()
{
    void (^blk)(void) = ^{
        printf("Block\n");
    };
    return 0;
}
Copy the code

C + + code:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

/ / block structure
struct __main_block_impl_0 {
    
  struct __block_impl impl;
    
  struct __main_block_desc_0* Desc;
  
  //Block constructor
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;/ / isa pointerimpl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};// Code inside the block to be called in the future: the block value is converted to C function code
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
}

static struct __main_block_desc_0 {
    
  size_t reserved;
  size_t Block_size;
    
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};

/ / the main function
int main()
{
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    return 0;
}
Copy the code

First let’s look at the C++ code converted from the original block value (OC code block) :

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("Block\n");
}
Copy the code

Here, *__cself is a pointer to the value of the Block, which is equivalent to the value of the Block itself (equivalent to self in C++ this, OC).

And it’s easy to see that __cself is a pointer to the __main_block_IMPL_0 structure implementation. The Block structure is the __main_block_IMPL_0 structure. The value of Block is constructed from __main_block_IMPL_0.

Let’s look at the declaration of this structure:

struct __main_block_impl_0 {
    
  struct __block_impl impl;
    
  struct __main_block_desc_0* Desc;
  
  // constructor
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

As you can see, the __main_block_IMPL_0 structure has three parts:

The first is the member variable impl, which is the actual function pointer to __main_block_func_0. Take a look at the declaration of its structure:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;  // Areas required for future version upgrades
  void *FuncPtr; // Function pointer
};
Copy the code

The second member variable is a Desc pointer to the __main_block_desc_0 structure, which is used to describe additional information about the current block, including the size of the structure and so on

static struct __main_block_desc_0 {
    
  size_t reserved;  // Areas required for future upgrades
  size_t Block_size;/ / the size of the block
    
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};
Copy the code

The third part is the constructor of the __main_block_IMPL_0 structure, which __main_block_IMPL_0 is the implementation of

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
Copy the code

In the constructor of this structure, the ISA pointer holds the pointer to an instance of the structure of the owning class. The __main_block_IMlp_0 struct is equivalent to an Objective-C class object struct, where _NSConcreteStackBlock is equivalent to an instance of a Block struct, meaning that a Block is an objective-C object implementation of a closure.

Block intercepts automatic variables and objects

Block intercepts automatic variables (local variables)

When you use a Block, you can use not only its internal parameters, but also local variables outside the Block. Once external variables are used inside a Block, they are saved by the Block.

Interestingly, even if these variables are modified outside the Block, the variables that exist inside the Block are not modified. Take a look at the code:

int a = 10;
int b = 20;
    
PrintTwoIntBlock block = ^(){
    printf("%d, %d\n",a,b);
};
    
block();/ / 10 20
    
a += 10;
b += 30;
    
printf("%d, %d\n",a,b);/ / 20 to 50
    
block();/ / 10 20
Copy the code

We can see that after we change the values of a and b externally, when we call the block again, the print inside is still the same as before. It feels as if the external-to-local variable is not the same as the variable intercepted inside the Block.

So what happens if you change the values of A and B internally?

int a = 10;
int b = 20;
    
PrintTwoIntBlock block = ^(){
    // Failed to compile
    a = 30;
    b = 10;
};
    
block();
Copy the code

Once a local variable is stored in a Block, it cannot be modified within the Block without additional action.

It is possible to change an object, such as adding an object to a mutable array inside a block without adding a __block modifier.

NSMutableArray *array = [[NSMutableArray alloc] init];
    
NSLog(@ "% @",array); / / @ []
    
PrintTwoIntBlock block = ^(){
    [array addObject:@1];
};
    
block();
    
NSLog(@ "% @",array);/ / @ [1]
Copy the code

OK, now we know three things:

  1. Blocks can intercept local variables.
  2. Modify local variables outside the Block. Local variables inside the Block are not affected.
  3. Modify Block internal to local variable, compile failed.

To illustrate point 2,3, let’s look at what happens when a Block intercepts a variable using C++ code:

int main()
{
    int dmy = 256;
    int val = 10;
    
    const char *fmt = "var = %d\n";
    
    void (^blk)(void) = ^{
        printf(fmt,val);
    };
    
    val = 2;
    fmt = "These values were changed. var = %d\n";
    
    blk();
    
    return 0;
}
Copy the code

C + + code:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  const char *fmt;  / / be added
  int val;          / / be added
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy

        printf(fmt,val);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};

int main()
{
    int dmy = 256;
    int val = 10;

    const char *fmt = "var = %d\n";

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

    val = 2;
    fmt = "These values were changed. var = %d\n";

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}
Copy the code

Take a look at __main_block_IMPL_0 separately:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt; // Intercepted automatic variables
  int val;         // Intercepted automatic variables
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  1. We can see that automatic variables (FMT, val) used in block internal syntax expressions are appended as member variables to the __main_block_IMPL_0 structure (note: automatic variables not used by block are not appended, such as dmy variables).
  2. When initializing an instance of a block structure (see the __main_block_IMPL_0 constructor), the intercepted automatic variables FMT and val are also needed to initialize an instance of the __main_block_IMPL_0 structure, as the block size increases with the addition of intercepted automatic variables.

Let’s look at the code for the function body:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  printf(fmt,val);
}
Copy the code

This makes it even more obvious: both FMT and var are derived from __cself, indicating that they are part of a block. And judging from the comments (which are automatically generated by Clang), these two variables are passed by value, not pointer, meaning that the Block only intercepts the value of the automatic variable, so this explains that changing the value of the automatic variable outside the Block does not affect the value inside the Block.

So why does changing the inside of a Block to a variable by default cause compilation to fail? My thinking is that since we can’t change the value of an external variable inside a Block, there is no need to change the value of a variable inside a Block, because the variables inside and outside the Block are actually two different things: the former is a member variable of the internal structure of the Block, and the latter is a temporary variable in the stack.

We now know that the values of intercepted automatic variables cannot be changed directly, but there are two ways to solve this problem:

  1. Changes variables stored in a particular storage area.
  2. Changed by the __block modifier.

1. Change the variables stored in the special storage area

  • Global variables that can be accessed directly.
  • Static global variables that can be accessed directly.
  • Static variable, direct pointer reference.

Let’s use OC and C++ code comparison to see the concrete implementation:

OC code:

int global_val = 1;// Global variables
static int static_global_val = 2;// Global static variables

int main()
{
    static int static_val = 3;// Static variables
    
    void (^blk)(void) = ^{
        global_val *=1;
        static_global_val *=2;
        static_val *=3;
    };
    return 0;
}
Copy the code

C + + code:

int global_val = 1;
static int static_global_val = 2;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  int *static_val = __cself->static_val; // bound by copy

  global_val *=1;
  static_global_val *=2;
  (*static_val) *=3;
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};

int main()
{
    static int static_val = 3;

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
    return 0;
}
Copy the code

And we can see,

  • Global variables and global static variables are not captured in the block and are accessed without the block (regardless of __cself) :
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  int *static_val = __cself->static_val; // bound by copy

  global_val *=1;
  static_global_val *=2;
  (*static_val) *=3;
}
Copy the code
  • To access a static variable (static_val), pass the pointer to the __main_block_IMPL_0 constructor and save it:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;// is a pointer, not a value
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

So what’s a way to assign a value to a variable inside a Block? — through the __block keyword. Before we get to the __block keyword, let’s talk about Block intercepting objects:

Block intercept object

Let’s look at the code that intercepts the array object in a block, array exists beyond its scope:

blk_t blk;
{
    id array = [NSMutableArray new];
    blk = [^(id object){
        [array addObject:object];
        NSLog(@"array count = %ld",[array count]);
            
    } copy];
}
    
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
Copy the code

Output:

block_demo[28963:1629127] array count = 1
block_demo[28963:1629127] array count = 2
block_demo[28963:1629127] array count = 3
Copy the code

Take a look at the C++ code:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;// Intercepted objects
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

It is worth noting that in OC, C constructs cannot contain variables that are modified by __strong, because the compiler does not know when C constructs should be initialized and discarded. But OC’s runtime library is able to catch exactly when blocks are copied from the stack to the heap, and when blocks on the heap are discarded, both through the __main_block_copy_0 and __main_block_dispose_0 functions:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
Copy the code

Where _Block_object_assign is equivalent to the retain operation, which assigns an object to a structure member variable of the object type. _Block_object_dispose is equivalent to the release operation.

When will these two functions be called?

function Called time
__main_block_copy_0 Copy from stack to heap
__main_block_dispose_0 Blocks on the heap are discarded

When are blocks on the stack copied to the heap?

  • Call block’s copy function
  • When a Block is returned as a function return value
  • When assigning a Block to a class with the __strong modifier ID or to a member variable of Block type
  • Cocoa framework methods that contain usingBlocks, or GCD apis that pass blocks

When do blocks become obsolete?

Call the dispose function when the Block on the heap is released and no one else holds it.

The __weak keyword:

{
        id array = [NSMutableArray new];
        id __weak array2 = array;
        blk = ^(id object){
            [array2 addObject:object];
            NSLog(@"array count = %ld",[array2 count]);
        };
    }
    
    blk([NSObject new]);
    blk([NSObject new]);
    blk([NSObject new]);
Copy the code

Output:

 block_demo[32084:1704240] array count = 0
 block_demo[32084:1704240] array count = 0
 block_demo[32084:1704240] array count = 0
Copy the code

Because array is released at the end of the variable’s scope, nil is assigned to Array2.

Implementation principle of __block

__block modifies local variables

Let’s look at the effect of adding the __block keyword to a local variable using OC code:

__block int a = 10;
int b = 20;
    
PrintTwoIntBlock block = ^(){
    a -= 10;
    printf("%d, %d\n",a,b);
};
    
block();/ / 0 to 20
    
a += 20;
b += 30;
    
printf("%d, %d\n",a,b);/ / 20 to 50block(); /10 20
Copy the code

As we can see, __block variables can be modified within blocks.

A variable after adding a __block is called a __block variable,

A brief explanation of what __block does: The __block specifier is used to specify which storage area to set the value of a variable to. That is, when an automatic variable is added to a __block specifier, the storage area of the automatic variable is changed.

Let’s use the clang tool to look at the C++ code:

OC code

int main()
{
    __block int val = 10;
    
    void (^blk)(void) = ^{
        val = 1;
    };
    return 0;
}
Copy the code

C + + code

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/); }static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy) (struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0.sizeof(__Block_byref_val_0), 10};

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    return 0;
}

Copy the code

What happens inside __main_block_IMPL_0?

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; > __main_block_IMPL_0 adds a member variable, which is a structure pointer to an instance of the __Block_byref_val_0 structure. So what is this structure? This structure is generated when the variable val is modified by __block. The structure is declared as follows: objcstruct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};
Copy the code

As we can see, the last member variable of this structure is equivalent to the original automatic variable. There are two member variables that need special attention:

  1. Val: preserves the original val variable, that is, the original int val variable is modified by __block to create a structure. One of the member variables of this structure holds the original val variable.
  2. __forwarding: With __forwarding, it is possible to access the __block variable correctly whether it is configured on the stack or heap, that is, __forwarding points to itself.

To visualize this, use a picture:

  1. Originally, when a __block variable is on the stack, its member variable __forwarding refers to an instance of the __block variable structure on the stack.
  2. When __block is copied to the heap, the value of __forwarding is replaced with the address of the structure instance of the target __block variable on the heap. The target __block variable on the heap has its own __forwarding value pointing to itself.

We can see that we have added a pointer to the __Block_byref_val_0 structure instance. Here //by ref the comment generated by clang indicates that it refers to the __Block_byref_val_0 structure instance val via a pointer.

Therefore the __Block_byref_val_0 structure is not in the __main_block_IMPL_0 structure, in order to make the __block variable used in multiple blocks.

Here’s an example:

int main()
{
    __block int val = 10;
    
    void (^blk0)(void) = ^{
        val = 12;
    };
    
    void (^blk1)(void) = ^{
        val = 13;
    };
    return 0;
}
Copy the code
int main()
{
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0.sizeof(__Block_byref_val_0), 10};

    void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    void (*blk1)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));
    return 0;
}
Copy the code

We can see that in main, both blocks refer to an instance of the __Block_byref_val_0 structure val.

So what happens when __block decorates an object?

__block decorates an object

An __block can specify any type of automatic variable. Let’s specify an object of type ID:

Look at the structure of the __block variable:

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id obj;
};
Copy the code

Copy and dispose methods for automatic variables of id type or object type modified by __strong:

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, * (void((* *)char*)src + 40), 131);
}


static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void((* *)char*)src + 40), 131);
}
Copy the code

Similarly, when a Block holds an id type or an object type automatic variable modified by __strong:

  • If the __block object variable is copied from the stack to the heap using the _Block_object_assign function,
  • When a __block object variable on the heap is discarded, the _Block_object_dispose function is used.
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

As you can see, obj is added to the __main_block_IMPL_0 structure, which is of type __Block_byref_obj_0.

Three kinds of Block

Careful students will notice that the isa pointer in the Block constructor __main_block_IMPL_0 above points to &_NSConcretestackblock, which indicates that the current Block is in the stack. In fact, there are three types of blocks:

Block of the class Storage domain Effect of copy
_NSConcreteStackBlock The stack Copy from stack to heap
_NSConcreteGlobalBlock The data area of the program Do nothing
_NSConcreteMallocBlock The heap Reference count increment

GlobalBlock: _NSConcreteGlobalBlock

Because the structure instance of the global Block is set in the program’s data store, it can be accessed anywhere in the program through a pointer.

  • Block syntax is used to describe global variables.
  • Block does not intercept automatic variables.

If one of the above two conditions is satisfied, a global Block can be generated. The following uses C++ respectively to show the first condition of the global Block:

C code:

void (^blk)(void) = ^{printf("Global Block\n"); };int main()
{
    blk();
}

Copy the code

C + + code:

struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;/ / globalimpl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
printf("Global Block\n"); }static struct __blk_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blk_block_desc_0_DATA = { 0.sizeof(struct __blk_block_impl_0)};

static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);
void (*blk)(void) = ((void (*)())&__global_blk_block_impl_0);

int main()
{
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
Copy the code

We can see that the isa pointer in the Block constructor is given &_nsconcreteglobalblock, indicating that it isa global Block.

Stack Block: _NSConcreteStackBlock

After a Block is generated, if the Block is not a global Block, it is an _NSConcreteStackBlock object, but if the scope of the variable to which it belongs ends, the Block is deprecated. The same is true for __block variables on the stack.

However, if a Block variable and a __block variable are copied to the heap, it is no longer affected by the end of the scope of the variable, because it becomes a heap Block:

Heap Block: _NSConcreteMallocBlock

After copying the stack block to the heap, the ISA member variable of the block structure becomes _NSConcreteMallocBlock.

What happens to the other two types of blocks after they are copied?

Block type Storage location Impact of the copy operation
_NSConcreteGlobalBlock The data area of the program Do nothing
_NSConcreteStackBlock The stack Copy from stack to heap
_NSConcreteMallocBlock The heap Reference count increment

In most cases, the compiler makes a judgment call and automatically copies blocks from the stack to the heap:

  • When a block is returned as a function value
  • Partially when you pass a block to a method or function
    • Cocoa framework methods and methods with usingBlock in their names.
    • Grand Central Dispatch API.

Except in both cases, we basically need to copy the block manually.

So what happens to the __block variable after the Block does the copy operation?

  1. When any block is copied to the heap, the __block variable is copied from the stack to the heap and held by the block.
  2. If other blocks are copied to the heap, the copied Block holds the __block variable and increases the __block reference count. In turn, if the Block is deprecated, the __block it holds is released (no more blocks reference it).

Block loop reference

An automatic variable of an object type that uses the __strong modifier inside a Block is held by the Block while it is copied from the stack to the heap.

So if the object also holds a Block, it is easy to loop references.

typedef void(^blk_t)(void);

@interface Person : NSObject
{
    blk_t blk_;
}

@implementation Person

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"self = %@".self);
    };
    return self;
}

@end
Copy the code

Block BLk_t holds self, which also holds BLk_t as a member variable

__weak modifier

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    blk_ = ^{
        NSLog(@"self = %@",weakSelf);
    };
    return self;
}
Copy the code
typedef void(^blk_t)(void);

@interface Person : NSObject
{
    blk_t blk_;
    id obj_;
}

@implementation Person
- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"obj_ = %@",obj_);// Circular reference warning
    };
    return self;
}
Copy the code

Obj_ in Block syntax intercepts self, because ojb_ is a member variable of self. Therefore, if a Block wants to hold obj_, it must reference self first, so it also creates a circular reference. It’s like if you want to go to a cafe in a mall, you need to know where the mall is.

What if an attribute uses the weak keyword?

@interface Person(a)
@property (nonatomic.weak) NSArray *array;
@end

@implementation Person

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"array = %@",_array);// Circular reference warning
    };
    return self;
}
Copy the code

There will still be a warning about circular references, because loops refer to things between self and block. It doesn’t matter if the block holds a strong or weak member variable, even the basic assign type.

@interface Person(a)
@property (nonatomic.assign) NSInteger index;
@end

@implementation Person

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"index = %ld",_index);// Circular reference warning
    };
    return self;
}
Copy the code

__block qualifier

- (instancetype)init
{
    self = [super init];
    __block id temp = self;Hold the self / / temp
    
    / / hold blk_ self
    blk_ = ^{
        NSLog(@"self = %@",temp);/ / blk_ holds a temp
        temp = nil;
    };
    return self;
}

- (void)execBlc
{
    blk_();
}
Copy the code

So if you don’t execute BLk_ (setting temp to nil), you can’t break the loop.

Once blK_ is executed, only

  • The self hold blk_
  • Blk_ holding temp

What are the advantages of using __block to avoid loops?

  • __block controls how long an object is held.
  • To avoid a circular reference, a block must be executed, otherwise the circular reference will always exist.

__block, __weak, or __unsafe_unretained depends on what the Block is used for.

Extended literature:

  1. Delve into how blocks capture external variables and implement __block
  2. Let’s get to the bottom of blocks
  3. Implementation of Objective-C Blocks

This post has been synced to my blog: Portal

— — — — — — — — — — — — — — — — — — — — — — — — — — — — on July 17, 2018 update — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Pay attention!!

The author recently opened a personal public account, mainly to share programming, reading notes, thinking articles.

  • Programming articles: including selected technical articles published by the author before, and subsequent technical articles (mainly original), and gradually away from iOS content, will shift the focus to improve the direction of programming ability.
  • Reading notes: Share reading notes on programming, thinking, psychology, and career books.
  • Thinking article: to share the author’s thinking on technology and life.

Because the number of messages released by the official account has a limit, so far not all the selected articles in the past have been published on the official account, and will be released gradually.

And because of the various restrictions of the major blog platform, the back will also be released on the public number of some short and concise, to see the big dry goods article oh ~

Scan the qr code of the official account below and click follow, looking forward to growing with you