The essence and underlying principle of block in oc

Posted by delxy on Wed, 22 Dec 2021 14:51:57 +0100

  • The essence of block

  • Type and storage area of block

  • __ The essence of block

  • Circular reference of block

preface:

The specific writing methods and usage scenarios of block will not be discussed here, because one day when you want to deeply understand the underlying principle of block, you have already written block dozens of times.

1, The essence of block:

block is an anonymous function with automatic variables.

Note:

Local variable = automatic variable (stack area) + static local variable (global area)

The automatic variable here refers to the external local variable captured in the block. Of course, you can not capture it

2, Type and storage area of block:

NSGlobalBlock: a block stored in the global area

Only when only external static local variables, global variables and static global variables are captured in the block, the block is saved Put it in the global area. Of course, if any type of external variables in the block are not captured, it is also in the global area.  

NSStackBlock: the block stored in the stack area

NSMallocBlock: stored in heap

Under arc, a block that intercepts external automatic variables is stored in the stack area when it is created. Then, if the block is modified by the strong/copy modifier, the system will copy the block from the stack area to the heap area, and point the pointer to the block in the heap area.

The system defaults to the strong type (whether it is a member variable block or a local variable block), so in most cases, blocks that have solved the puzzle of external automatic variables are stored in the heap. Only when you manually modify a local block with the weak attribute, it will not be copied to the heap.

Note: the five memory areas of ios are stack area, heap area, global area, constant area and code area

III__ The essence of block

Let's talk about the nature of the object intercepted by block

  1. Local variables:
    1. Automatic variable:
      1. Basic type: intercept its value
      2. Object type: intercept pointer
    2. Static local variables: intercepting pointers
  2. Static global variables: intercepting pointers
  3. Global variables: intercepting pointers
  4. Member variables: intercepting pointers

It can be seen here that only when intercepting the basic type data of automatic variables (such as int), the block intercepts only its value. Of course, if you only intercept its value and don't get its memory address, you can't modify it in the block (such as + 1).

Special attention should be paid to:

When an object is intercepted in the block code block modified by strong/copy, it will hold the object and make its reference count + 1.

__ The essence of block

When we intercept the basic type data of automatic variables and want to modify it in the block, this time__ Block comes in handy.

Take a look at the basic type data__ block modified source code

__block int i = 0;

Source code:
struct __Block_byref_i_0 {
  void *__isa;  //isa pointer
__Block_byref_i_0 *__forwarding; //The pointer points to itself
 int __flags;  //sign
 int __size;  //size
 int i; // Variable value
};
//New people just look at the top

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref

        (i->__forwarding->i) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));

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

    return 0;
}

From the source code, we know that the system will have__ The variable of block is converted into a block structure. And when the block is copied to the heap, let's do it together_ The structure of the block variable is also copied to the heap. Of the block structure in the heap_ forwarding pointer

In this way, it can be intercepted in the block__ In the structure of block variable__ forwarding pointer, which points to the heap_ Block variable. In this way, block can modify the variable.

Circular reference of block

First, let's look at a piece of code

//Block1 and block2 are the member variables of self, and block3 is the member variable of person class
//Will cause circular references
self.block1 = ^{
    NSLog(@"%@",self);
};

//It also causes circular references
self.block1 = ^{
    NSLog(@"%@",_blcok2);
};

//It also causes circular references
Person *person = [[Person alloc] init];
person.block3 = ^{
    NSLog(@"%@",person);
};

Let's take a look at how this circular reference is generated

Look at the first one. Block1 is a member variable in self, so self holds block1, strong reference, and reference count + 1 Then you can see that self is referenced in the block1 code. As mentioned above, copy/strong (default) modified blocks will be used, and intercepted objects will be strongly referenced, forming a closed-loop self - > block1 - > self. As a result, self and block1 reference each other and will not be automatically released.

Look at the second one_ Block2 is equivalent to self - > block2, so self - > block1 - > self - > block2 is formed. Obviously, this is also a closed loop, that is, circular reference.

Looking at the third one, we can see that the third one also forms a closed loop person - > block3 - > person.

Therefore, the culprit of circular reference is not self, but whether mutual strong references are generated. Self is not wrong, but self is included in the closed loop of circular reference that often occurs in the development process.

So how to avoid circular references?

Since they are caused by strong references to each other, let's change one of them to weak references

__weak __typeof(self) weakSelf = self; //Declare a weak reference pointer

self.block1 = ^{
    NSLog(@"%@",weakSelf);
};

At this time, let's take a look at the references between them, self - > block1 - > weakself.

At this time, weakSelf is a weak reference. When it points to its member variable block1, it is no longer a strong reference. At this time, the strong reference loop is broken.

Of course, if we only call local variables in the block, we don't need to declare weak reference pointers to self.

Reference article:

https://www.jianshu.com/p/ee9756f3d5f6

https://blog.csdn.net/olsQ93038o99S/article/details/83829415

https://blog.csdn.net/jingqiu880905/article/details/51997126

https://www.jianshu.com/p/53cedd7bafa4

Topics: objective-c