Dry Goods Trilogy of Objective-C Advanced Programming (2): Blocks

Keywords: Programming iOS OS X C


Objective-C Advanced Programming: iOS and OS X Multithreading and Memory Management

This chapter explains Block-related knowledge. Because the author converted Objective-C code into C++ code, it was very hard to read it for the first time, and I don't remember how many times I read it myself.

This summary is not only the content of this book, but also the related knowledge of Block that I have read in other blogs. It also adds my own understanding, and the structure of the article is not consistent with the original book. It is arranged and rearranged by me.

Take a look at the structure of this article (Blocks section):


The Dry Goods Trilogy of Objective-C Advanced Programming

Need to know first

The Method of Transforming Objective-C to C++

Because you need to look at the C++ source code of the Block operation, you need to know the method of transformation. Turn around and see for yourself:

  1. Write the code in OC source file block.m.
  2. Open the terminal and cd to the folder where block.m is located.
  3. Enter clang-rewrite-objc block.m, and the corresponding block.cpp file will be automatically generated in the current folder.

On the Characteristics of Several Variables

Variables that may be used in functions in c language:

  • Parameters of functions
  • Automated Variables (Local Variables)
  • Static variables (static local variables)
  • static global
  • global variable

Moreover, because of the special storage area, three of these variables can be invoked at any time and in any state:

  • Static variables
  • static global
  • global variable

The other two have their respective scopes, beyond which they will be destroyed.

Well, with these two points in mind, it's easier to understand the following.

The Essence of Block

First come to the conclusion: Block is the object realization of closure by Objective-C. In short, Block is the object.

The following is an analysis from the surface to the bottom:

Surface Analysis Block: It's a type

Block is a type that, once used, generates values that can be assigned to variables of Block type. For instance:

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

If we often use the same type of block in our projects, we can abstract this type of block with typedef:

typedef int(^AddOneBlock)(int count);

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

In this way, the assignment and delivery of blocks become relatively convenient, because the type of blocks has been abstracted.

Deep Analysis of Block's Essence: It's Object-C

Block is actually an Objective-C object because it contains isa pointers in its structure.

Let's turn Objective-C code into C++ code to see the implementation of block.

OC code:

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

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 pointer
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

};

//Code inside the block to be called in the future: function code whose block value is converted to C
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)};

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

First, let's look at the C++ code transformed from the original block value (OC code block):

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("Block\n");
}

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

And it's easy to see that cself is a pointer to the implementation of the main_block_impl_0 structure.
In conjunction with the previous sentence, that is to say, the Block structure is the main_block_impl_0 structure. The value of a Block is constructed by main_block_impl_0.

Let's take a look at the statement 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;
  }
};

As can be seen, the _main_block_impl_0 structure has three parts:

The first is the member variable impl, which is the actual function pointer pointing to _main_block_func_0. Look at the statement of its structure:

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

The second is that the member variable is the Desc pointer to the _main_block_desc_0 structure, which is used to describe the additional information of 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;//block size

} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

The third part is the constructor of the main_block_impl_0 structure. The main_block_impl_0 is the implementation of the block.

__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;
  }

In the constructor of this structure, the isa pointer holds the pointer of the instance of the structure of the class to which it belongs. _ The main_block_imlp_0 structure is equivalent to the structure of the Object-C class object, where the _NSConcreteStackBlock is equivalent to the structure instance of the Block, that is to say, the block is actually the object implementation of Object-C for closures.

Block intercepts automatic variables and objects

Block intercepts automatic variables (local variables)

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

Interestingly, even if these variables are modified outside the Block, the variables that exist inside the Block will not be 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 50

block();//10 20

We can see that when we call the block again after changing the values of a and b externally, the print inside is still the same as before. The impression is that the external to local variables are not the same as the variables intercepted inside the block.

So what happens to the value of a and b if we change it internally?

int a = 10;
int b = 20;

PrintTwoIntBlock block = ^(){
    //Compilation failed
    a = 30;
    b = 10;
};

block();

If no additional operations are performed, once the local variables are saved by the Block, they cannot be modified inside the Block.

However, it should be noted that the modification here refers to the assignment operation of the whole variable, and the operation of changing the object is allowed, such as adding objects to the variable array inside the block without the _block modifier.

NSMutableArray *array = [[NSMutableArray alloc] init];

NSLog(@"%@",array); //@[]

PrintTwoIntBlock block = ^(){
    [array addObject:@1];
};

block();

NSLog(@"%@",array);//@[1]

OK, now we know three things:

  1. Block can intercept local variables.
  2. Modify the local variables outside the Block, and the intercepted local variables inside the Block are not affected.
  3. Modify Block internal to local variables, compilation fails.

To explain points 2 and 3, we use C++ code to see what happens when Block intercepts variables:
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;
}

C++ code:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;

  const char *fmt;  //Added
  int val;          //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;
}

Extract _main_block_impl_0 separately to see:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt; //Intercepted Automated Variables
  int val;         //Intercepted Automated 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;
  }
};
  1. We can see that the automatic variables (fmt, val) used in the internal grammatical expression of a block are appended as member variables to the _main_block_impl_0 structure (note: automatic variables that are not used by the block will not be appended, such as dmy variables).
  2. When initializing the block structure instance (see the constructor of main_block_impl_0), intercepted automatic variables fmt and val are also needed to initialize the main_block_impl_0 structure instance, because the volume of the block will increase with the increase of intercepted automatic variables.

Let's look at the code for the body of the function:

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);
}

From this point of view, it is more obvious: FMT and VaR are all obtained from _csel, which shows that they belong to block. And from the perspective of annotations (annotations are generated automatically by clang), these two variables are value passing, not pointer passing, that is to say, the Block only intercepts the value of the automatic variable, so this explains that even if the value of the external automatic variable is changed, it will not affect the value inside the Block.

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

Now we know that the value of the intercepted automatic variable cannot be directly modified, but there are two ways to solve this problem:

  1. Change the variables stored in a particular storage area.
  2. Change by the _block modifier.

1. Changing variables stored in special storage areas

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

We also use OC and C++ code comparison to see the specific implementation:

OC code:

int global_val = 1;//global variable
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;
}

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;
}

We can see that,

  • Global variables and global static variables are not intercepted into the block, and their access is not blocked (independent of _csel):

    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;
    }
  • When accessing static_val, the pointer to the static variable is passed to the constructor of the _main_block_impl_0 structure and saved:

    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *static_val;//It's 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;
    }
    };

So what is the way to assign variables inside a Block? Through the block keyword. Before explaining the block keyword, explain the block interception object:

Block intercepts objects

Let's take a look at the code that intercepts the array object in the block, and the 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]);

Output:

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

Take a look at the C++ code:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;//Objects intercepted
  __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;
  }
};

It is worth noting that in OC, the C structure cannot contain variables modified by strong, because the compiler does not know when to initialize and discard the C structure. However, OC runtime library can accurately grasp the time when blocks are copied from stack to heap and blocks on heap are discarded. In implementation, blocks are copied through main_block_copy_0 function and _main_block_dispose_0 function:

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*/);
}

Where _Block_object_assign is equivalent to retain operation, assigning objects to structural member variables of object type.
_ Block_object_dispose is equivalent to release operation.

When are these two functions called?

function Time to be called
__main_block_copy_0 Copy from stack to heap
__main_block_dispose_0 When Block s on the heap are discarded

When will the Block on the stack be copied to the heap?

  • When calling copy function of block
  • When Block returns as a function return value
  • When assigning a Block to a class with the _strong modifier id type or a member variable of the Block type
  • When the method contains the Cocoa framework method of using Block or passes the Block in the API of GCD

When was Block abandoned?

When the Block on the heap is released, no one will call the dispose function when they hold the Block.

_ 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]);

Output:

 block_demo[32084:1704240] array count = 0
 block_demo[32084:1704240] array count = 0
 block_demo[32084:1704240] array count = 0

Because array is released at the end of the variable scope, nil is assigned to array 2.

_ The Realization Principle of block

_ block modifies local variables

First, through OC code to see the effect of adding _block keyword to local variables:

__block int a = 10;
int b = 20;

PrintTwoIntBlock block = ^(){
    a -= 10;
    printf("%d, %d\n",a,b);
};

block();//0 20

a += 20;
b += 30;

printf("%d, %d\n",a,b);//20 50

block();/10 20

We can see that the _block variable can be modified inside the block.

The variable after adding a block is called a block variable.

Let's briefly talk about the role of block s: The block descriptor is used to specify which storage area the variable value is set to, that is, when the automatic variable is added with the _block descriptor, the storage area of the automatic variable is changed.

Next, let's take a look at the C++ code with the clang tool:

OC code

int main()
{
    __block int val = 10;

    void (^blk)(void) = ^{
        val = 1;
    };
    return 0;
}

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;
}

What happens in _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 It adds a member variable, which is a structure pointer, pointing to __Block_byref_val_0 An example of a structure. So what is this structure?

//This structure is generated after the variable val is modified by _block!!
//The structure is declared as follows:
```objc
struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

We can see that the final member variable of this structure is equivalent to the original automatic variable.
Here are two member variables that need special attention:

  1. Val: Save the original val variable, that is to say, the original simple int type Val variable is modified by _block to generate a structure. One of the member variables in this structure holds the original val variable.
  2. Forwarding: By forwarding, block variables can be accessed correctly either on the stack or on the heap, i.e. _forwarding is directed to itself.

Let's use a picture to visualize:


Image from: Objective-C Advanced Programming: iOS and OS X Multithreading and Memory Management


How to achieve it?

  1. Initially, when a block variable is on the stack, its member variable forwarding points to an instance of the _block variable structure on the stack.
  2. When _block is copied to the heap, the forward value is replaced by the address of the target block variable on the heap with the structure instance. The value of the forward of the target block variable on the heap points to itself.

As you can see, a pointer to an instance of the Block_byref_val_0 structure has been added. Here //by ref, a clang-generated comment, indicates that it refers to the Block_byref_val_0 structure instance Val through a pointer.

Therefore, the Block_byref_val_0 structure is not in the main_block_impl_0 structure, and the purpose is to use the _block variable in multiple Blocks.

For instance:

int main()
{
    __block int val = 10;

    void (^blk0)(void) = ^{
        val = 12;
    };

    void (^blk1)(void) = ^{
        val = 13;
    };
    return 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 (*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;
}

We can see that in the main function, both blocks refer to the instance Val of the _Block_byref_val_0 structure.

So what happens when _block modifies an object?

_ block modifies objects

_ 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 and dispose methods of id type or object type automatic variables 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);
}

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

  • If the _block object variable is copied from the stack to the heap, the _Block_object_assign function is used.
  • When the _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;
  }
};

As you can see, obj is added to the main_block_impl_0 structure, which is of type Block_byref_obj_0.

Three Block s

Careful students will find that the isa pointer in Block's 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:

Classes of Block Storage domain Copy effect
_NSConcreteStackBlock Stack From stack copy to heap
_NSConcreteGlobalBlock Data area of program Do nothing
_NSConcreteMallocBlock heap Reference Count Increase

Global Block:_NSConcreteGlobalBlock

Because the structure instance of the global Block is set in the data storage area of the program, it can be accessed through the pointer at any place of the program. Its generation condition is:

  • When there is a block grammar where global variables are described.
  • block does not intercept automatic variables.

The above two conditions can generate global blocks if only one of them is satisfied. Here we show the global blocks under the first condition with C++:

c code:

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

int main()
{
    blk();
}

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;//Overall situation
    impl.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);
}

We can see that the isa pointer in the Block structure constructor is assigned to &_NSConcreteGlobalBlock, indicating that it is a global Block.

Stack Block:_NSConcreteStackBlock

After generating a Block, if the Block is not a global Block, it is a _NSConcreteStackBlock object, but if the variable to which it belongs ends with the domain name, the Block is discarded. So is the _block variable on the stack.

However, if the Block variable and _block variable are copied to the heap, they will no longer be affected by the end of the variable scope because they become heap blocks:

Heap Block:_NSConcreteMallocBlock

After the stack block is copied to the heap, the isa member variable of the block structure becomes _NSConcreteMallocBlock.

What happens when the other two types of Block s are copied?

Block Types Storage location The effect of copy operation
_NSConcreteGlobalBlock Data area of program Do nothing
_NSConcreteStackBlock Stack From stack copy to heap
_NSConcreteMallocBlock heap Reference Count Increase

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

  • When a block is returned as a function value
  • In some cases, when a block is passed to a method or function
    • The method of the Cocoa framework and the method name contains usingBlock and so on.
    • The API of Grand Central Dispatch.

In addition to these two cases, we basically need to manually copy the block.

So what happens to the _Block variable when the Block performs a 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 then copied to the heap, the replicated block holds the block variable and increases the reference count of the block, which in turn releases the _block it holds (no more blocks refer to it).

Block circular reference

If the _strong modifier is used inside the Block as an automatic variable of the object type, the object will be held by the Block when the Block is copied from the stack to the heap.

So if the object also holds a Block at the same time, it is prone to circular 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

Block blk_t holds self, while self 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;
}
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;
}

The obj_in the block grammar intercepts self because ojb_is a member variable of self, so if a block wants to hold obj_, it must refer to self first, so it also causes circular references. It's like if you want to go to a cafe in a mall, you need to know where the mall is first.

What if a property uses the weak keyword?

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

@implementation Person

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"array = %@",_array);//Circular Reference Warning
    };
    return self;
}

There are warnings about circular references, because circular references refer to things between self and block. It doesn't matter whether the member variable held by the block is strong or weak, and even the basic type (assign) is the same.

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

@implementation Person

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"index = %ld",_index);//Circular Reference Warning
    };
    return self;
}

_ block modifier

- (instancetype)init
{
    self = [super init];
    __block id temp = self;//temp holds self

    //self holds blk_
    blk_ = ^{
        NSLog(@"self = %@",temp);//blk_holds temp
        temp = nil;
    };
    return self;
}

- (void)execBlc
{
    blk_();
}

So if blk_ (temp is set to nil), you can't break the cycle.

Once blk_ is executed, only

  • self holds blk_
  • blk_holds temp

What are the characteristics of using _block to avoid circular comparisons?

  • The holding time of the object can be controlled by _block.
  • To avoid circular references, a block must be executed, otherwise circular references will always exist.

So we should decide whether to use block or weak or _unsafe_unretained according to the actual situation and the current use of block.

In this way, the second chapter is finished, and I always feel that there is something missing, which will be continuously updated and improved in the future.
The last article in this series will be released next Monday.^^

Extended literature:

  1. In-depth study of the principle of Block capturing external variables and _block implementation
  2. Let's go into block s.
  3. On the Realization of Objective-C block

This article has been synchronized to personal blogs: Portal

This article has been registered in copyright printing. If you need to reproduce it, please visit copyright printing. 4842 2928

Acquisition of authorization

Posted by eazyefolife on Mon, 08 Jul 2019 12:49:53 -0700