This is the 24th day of my participation in the August Text Challenge.More challenges in August

In our actual development, the utilization rate of blocks is quite high. When we use blocks, we will encounter various problems, such as classic circular application. Then how do these problems arise and how do we solve them, which requires us to have a deep understanding of blocks to better solve these problems. Today we’ll take a closer look at blocks.

1. Block type

1.1 type

  • A global Block, NSGlobalBlock, is located in a global area and does not use external variables inside the Block, or just use static variables and global variables.
  • The heap Block, NSMallocBlock, is located in the heap and uses variables or OC attributes within the Block and assigns values to strongly referenced or copy-modified variables.
  • Stack blocks, nsStackblocks, are located on the stack and, like heap blocks, use variables or OC attributes within the Block, but cannot be assigned to strongly referenced or copy-modified variables.

1.2 example 1

Global blocks are easy to distinguish, whereas mallocblocks and stack blocks are not

- (void)blockRetaincount{
    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    void(^strongBlock)(void) = ^{
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();
    
    void(^__weak weakBlock)(void) = ^{ // + 1
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
}
Copy the code

What is the print time here? Let’s run it and see what it looks like, as shown below

Why is this the case? Now let’s analyze it in detail

  • NSObject *objc = [Nsobject new];After that, the reference count for obj is 1
  • void(^strongBlock)(void) = ^{ NSLog(@”—%ld”,CFGetRetainCount((__bridge CFTypeRef)(objc))); }; Why is it 3 here? Because strongBlock grabs objC externally and copies it, the reference count is +1, strongBlock is a heap Block (mallocBlock) and it holds strongly and copies it from the stack to the heap, at which point the object reference count on the heap is +1 again and becomes 3.
  • void(^__weak weakBlock)(void) = ^{ // + 1 NSLog(@”—%ld”,CFGetRetainCount((__bridge CFTypeRef)(objc))); }; This is a stack Block, because weakBlock captures objC externally and copies it. The reference count is +1, but it is a stack Block and will not be copied from the stack to the heap, so 4 is printed here.
  • void(^mallocBlock)(void) = [weakBlock copy]; WeakBlock is copied from the stack to the heap. At this time, the object reference count on the heap is +1, so 5 is printed here.

1.3 sample 2

Let’s look at another piece of code, as shown below

- (void)blockCopy{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _RoBlock *blc = (__bridge struct _RoBlock *)weakBlock;
    id __strong strongBlock = weakBlock;
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
}
Copy the code

How does this code work? After we run it, the result is shown below

There’s a crash and flash back. Let’s analyze itblc -> invokeThe invoke here performs the invocation.struct _RoBlock *blc = (__bridge struct _RoBlock *)weakBlock;Here, weakBlock is transformed strongly.id __strong strongBlock = [weakBlock copy];WeakBlock is copied here.blc->invoke = nil;Empty BLC invoke.void(^strongBlock1)(void) = strongBlock;This code is just strong once to put strongBlock with block properties that can be called to execute. It indicates that when weakBlock is strongly transformed into BLC, they still point to the same memory area. BLC invoke is nil, so weakBlock invoke is nil, so flash back when called here.

How to solve it? Id __strong strongBlock = weakBlock; Id __strong strongBlock = [weakBlock copy]; Let’s try it again, as shown here

BLC ->invoke = nil; BLC ->invoke = nil; Before, copy was performed.

1.4 sample 3

Let’s examine another piece of such code, as shown below

- (void)blockDemo{
//    NSObject *a = [NSObject alloc];
    int a = 0;
    void(^__weak weakBlock)(void) = nil;
    {
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"strongBlock ---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1");
    }
    weakBlock();
}
Copy the code

Let’s take a look at the implementation:

Let’s analyze its execution process:

  • void(^__weak weakBlock)(void) = nil; This is a stack Block
  • void(^__weak weakBlock)(void) = nil;

    {

    void(^__weak strongBlock)(void) = ^{

    NSLog(@”strongBlock —%d”, a);

    };

    weakBlock = strongBlock;

    NSLog(@”1″);

    }

    weakBlock();

    } here is a code block, andvoid(^__weak weakBlock)(void) = nil;Has nothing to do
  • In code blocks strongBlock is a Block Block
  • StrongBlock gives weakBlock, weakBlock is also a stack Block
  • The life cycle of the stack Block is in the blockDemo method, which means that the life cycle of the stack Block will not end until the method is out, so it is correct to run it here.

Let’s change the code as follows

- (void)blockDemo{
//    NSObject *a = [NSObject alloc];
    int a = 0;
    void(^__weak weakBlock)(void) = nil;
    {
        void(^ strongBlock)(void) = ^{
            NSLog(@"strongBlock ---%d", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1");
    }
    weakBlock();
}
Copy the code

After the operation

There’s a flash back. WeakBlock = strongBlock; weakBlock = strongBlock; weakBlock = strongBlock; weakBlock = strongBlock; weakBlock = strongBlock; weakBlock = strongBlock; When assigned, weakBlock is also in the heap, so calling weakBlock() here will flash back. WeakBlock = strongBlovk; WeakBlock = [strongBlock copy]; It’s also going to flash back, for the same reason, because it’s also in the heap, and it’s going to get destroyed.

2. Analysis of Block circular reference

2.1 Why does circular Reference occur

First let’s take a look at the graph below

When B wants to release it, it needs to wait for A to send A release signal. If B’s reference count is 0, dealloc will be called. Only when A is in dealloc will send A signal to B.

This diagram describes that A holds B, and B also holds A, which forms an unreleasable cycle, because the release of B depends on the release of A, which in turn depends on the release of B, resulting in A circular reference.

2.2 the sample

Example 1

- (void)test1 { self.name = @"robert"; self.block = ^(void) { NSLog(@"%@", self.name); }; self.block(); } - (void)dealloc{NSLog(@"dealloc "); }Copy the code

Running the above code, dealloc is not called, indicating that a circular reference is generated. Take a look at the code example 2 below

[UIView animateWithDuration:1 animations:^{
        NSLog(@"%@", self.name);
    }];
Copy the code

Operation effect is shown in figureHere you can see that dealloc is called, so no circular reference is generated. Why? So let’s analyze it. In example 1,self->block->selfGenerating circular references__weak typedef(self) weakSelf = self;Let’s change the code again as follows:

- (void)test1 {
    __weak typeof(self)weakSelf = self;
  self.name = @"robert";
  self.block = ^(void) {
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
          NSLog(@"name=%@", weakSelf.name);
      });
  };
  self.block();
}
Copy the code

Take a look at the resultsThe value of name is printed as nil analysis: When we return to the last page, self is released, weakSelf is released, and then call nil. We can get through__strong __typeof(weakSelf)strongSelf = self;To solve, the code is as follows

- (void)test1 {
    __weak typeof(self)weakSelf = self;
    self.name = @"robert";
    self.block = ^(void) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"name=%@", strongSelf.name);
        });
    };
    self.block();
}
Copy the code

The results are as follows

This is going to work fine. So strong-strong-dance strongSelf is a strong reference, it’s a temporary variable, its lifetime is within the scope of the block, it gets released automatically. Let’s talk about the second method. The code is as follows

 __block SecondViewController *vc = self;
    self.name = @"robert";
    self.block = ^(void) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"name=%@", vc.name);
            vc = nil;
        });
    };
    self.block();
}
Copy the code

The result is as follows

Vc is held by self.block, but we empty vc to break the loop. Self ->blovk->vc->self,vc=nil breaks the loop chain. External variable operations can only be modified by adding __block. Of course, we can also pass parameters to solve the problem. Let’s look at one more piece of code.

static SecondViewController *staticSelf_; - (void)blockWeakStatic { __weak typeof(self) weakSelf = self; staticSelf_ = weakSelf; } - (void)dealloc{NSLog(@"dealloc "); }Copy the code

WeakSelf ->self -> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf-> staticSelf WeakSelf here is actually Self. Print the breakpoint, as shown below

It can be seen that self, weakSlef, and staticSelf point to the same memory area, that is, weakSelf and self are mapped. The three of them form a circular chain, so a circular reference is generated. Let’s look at the following code

- (void)block_weak_strong {
    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@"111111111----- %@", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}
Copy the code

Does this code generate circular references? And the answer is yes, so let’s run it and see what happens

Let’s analyze:

  • Self. DoWork holds on self
  • __strong typeof(self) strongSelf = weakSelf; weakSelf.doStudent = ^{ NSLog(@”111111111—– %@”, strongSelf); }; weakSelf.doStudent(); StrongSelf holds strongSelf against weakSelf. Although strongSelf is a temporary variable, its life cycle is in the block doWork. Before strongSelf is released, the above code will be executed, and doStudent will hold strongSelf. The strongSelf reference count is now 2. The strongSelf reference count will be -1 at the end of the doWork block, but the strongSelf will not be released
  • We can do it atNSLog(@"111111111----- %@", strongSelf);So I’m going to put strongSelf=nil, and I’m going to open the loop.