preface

Blocks are familiar to iOS developers. In normal development, we often use blocks as callbacks, as properties, or as arguments to methods. Of course, questions related to blocks are often asked in interviews, such as the classification of blocks. In this article, we will conduct basic exploration of Block, mainly divided into two aspects:

  • What kinds of blocks are there
  • Block circular reference problem

I. Classification of blocks

Does the block still have categories? Of course there is. To verify this, let’s create a new iOS project and, in the viewDidLoad method, write the following code:

int a = 10;

void (^myBlock)(void) = ^{
};

void (^testBlock)(void) = ^ {NSLog(@"%d", a);
};

NSLog(@ "% @", myBlock);
NSLog(@ "% @", testBlock);
Copy the code

Printing the two blocks separately produces the following result:

__NSGlobalBlock__ and __NSMallocBlock__ (global block and heap block) are two different types of __NSGlobalBlock__ and __NSMallocBlock__, respectively.

The only difference is that testBlock refers to an external local variable, A. What about commenting out the code in testBlock? The results are as follows:

It turns out that testBlock is a global block as long as no external local variables are referenced. So let’s try again. TestBlock references a global or static variable:


int globalA = 20; / / new
static int b = 30; / / new

int a = 10;
void (^myBlock)(void) = ^{
};

void (^testBlock)(void) = ^ {NSLog(@"%d", a);
};

/ / new
void (^globalVarBlock)(void) = ^ {NSLog(@"%d", globalA);
};

/ / new
void (^staticTestBlock)(void) = ^ {NSLog(@"%d", b);
};

NSLog(@ "% @", myBlock);
NSLog(@ "% @", testBlock);
NSLog(@ "% @", globalVarBlock); / / new
NSLog(@ "% @", staticTestBlock); / / new
Copy the code

The print result is as follows:

It turns out that when referencing global and static variables,globalVarBlockandstaticTestBlockIt’s still a global block. So you can see,Whether a block is a global block depends on whether it captures external local variables.

Stack, if you have a stack block, do you have a stack block? B: of course. In the demo above, add another example to test it out:

int a = 10;
void (^testBlock)(void) = ^ {NSLog(@"%d", a);
};

void (^ __weak weakBlock)(void) = ^ {NSLog(@"%d", a);
};

NSLog(@ "% @", testBlock);
NSLog(@ "% @", weakBlock);
Copy the code

The print result is as follows:

The result shows that when the __weak modifier is added, the type of weakBlock becomes __NSStackBlock__, also known as stack block.

We continue to test, copy weakBlock to another block, the code is as follows:

int a = 10;
void (^testBlock)(void) = ^ {NSLog(@"%d", a);
};

void (^ __weak weakBlock)(void) = ^ {NSLog(@"%d", a);
};

void (^ copyBlock)(void) = [weakBlock copy];

NSLog(@ "% @", testBlock);
NSLog(@ "% @", weakBlock);
NSLog(@ "% @", copyBlock);
Copy the code

The result is as follows:Obviously, when you copy a stack block to a variable, the new variable is no longer a stack block, but a heap block. TestBlock is a strong reference, copyBlock is a copy, and both are heap blocks, so to speakThe difference between a heap block and a stack block is whether it is a strong reference or copy.

However, this result is not convincing because we are using local variables. Let’s take a look at the case where a block is a return value and an attribute.

1. As a method return value

__weak: __weak: __weak: __weak: __weak: __weak: __weak

- (void(^) (void))getBlock {
    int a = 10;
    return^ {NSLog(@"%d", a);
    };;
}

void (^ returnBlock)(void) = [self getBlock];
void (^ __weak weakReturnBlock)(void) = [self getBlock];

NSLog(@ "% @", returnBlock);
NSLog(@ "% @", weakReturnBlock);
Copy the code

The print result is as follows:

The results show that block is copied to the heap when returned as a method parameter, with or without the __weak modifier.

Create 3 block attributes, copy, strong, weak, and assign the same block to each attribute.

@property (nonatomic.copy) void (^blockCopy)(void);
@property (nonatomic.strong) void (^blockStrong)(void);
@property (nonatomic.weak) void (^blockWeak)(void);

self.blockCopy = ^{
    NSLog(@"%d", a);
};

self.blockStrong = ^{
    NSLog(@"%d", a);
};

self.blockWeak = ^{
    NSLog(@"%d", a);
};

NSLog(@ "% @".self.blockCopy);
NSLog(@ "% @".self.blockStrong);
NSLog(@ "% @".self.blockWeak);
Copy the code

The print result is as follows:

The result is also a heap block.

According to the test results, it can be summarized as follows:

  • Blocks can be divided into three types,Global block, heap block, and stack block, corresponding to__NSGlobalBlock__,__NSMallocBlock__and__NSStackBlock__Three types of
  • A global block is one that does not reference external local variables, or refers only to static or global variables
  • A heap block or stack block that uses an external local variable is a heap block or stack block.
    • A stack block that uses an __weak modifier inside a function
    • Assigns a strong reference or manual copy to a heap block
    • Heap blocks are also returned as values or properties of methods

Block and circular reference

One of the most important aspects of iOS is reference counting, which means that when an object is alloc, copy, or retained, its reference count is incremented by one. When an object’s reference count reaches zero, it is no longer referenced and can be released.

If the reference relationship is one-way, such as A -> B -> C, then there is no problem. However, in real development, there are often situations where two objects refer to each other, such as A<=>B or A -> B -> C -> A, which form A closed loop of references, i.e. circular references. If the loop cannot be broken, the result is that the object cannot be freed, causing A memory leak.

When you think of circular references, the first thing that comes to mind is blocks, and when you think of blocks, circular references come to mind as well. It can be seen that the connection between the two is very close. In fact, improper use of blocks does cause circular references, but OC can cause circular references in other ways than blocks, such as proxies. Let’s explore how circular references can be caused and how to solve the problem.

2.1 How do I detect circular References

To do a good job, he must sharpen his tools. Before we explore circular references, we first need to know how to detect circular references, and there are two methods we can use.

  • 1. The first and best known method is to override the dealloc method in the class, which is the destructor of the OC class. This method is called when the object is released, but not if the object is not released. This approach is very familiar and will not be demonstrated in this article.

    • In this way, it is very accurate to detect whether the object is free or not, and once it is found that the method is not called, it is possible to consider whether a circular reference has occurred.
    • One limitation of this approach, however, is that we need to know in advance which classes need to be detected, which is not easy to do if we want to see if there is a memory leak in the project.
  • 2. Another is to check using the Allocations tool in XCode’s performance-testing tool Instruments, where you start engineering, and we detect how runtime objects were opened and freed, conveniently without knowing. The demo in this article uses XCode 13.1. To Open Instruments, go to the XCode menu on the upper left -> Open Developer Tool -> Instruments.

After selecting according to Step 1 and Step 2, click Choose to enter the next interface:

As shown in the figure, the LeakViewController page is entered twice. The second time, without exiting, there should be only one object in memory, but the result shows two. This means that LeakViewController and LeakShowTool are not released on the first exit and can be checked to see if a circular reference has occurred (there is indeed a circular reference, written specifically for testing purposes).

2.2 Why does the proxy not use Strong

We usually use weak to modify the proxy, but why? What if, instead of weak, you use strong or copy? We can experiment (in fact, we used this example in the previous section) by changing the property modifier of a broker to strong as follows:

/ / LeakShowTool parts
@protocol LeakProtocol <NSObject>

- (void)show;

@end

@interface LeakShowTool : NSObject

@property (nonatomic.strong) id<LeakProtocol> delegate;

@end

/ / LeakViewController parts
@interface LeakViewController()"LeakProtocol>

@property (nonatomic.strong) LeakShowTool *tool;

@end

@implementation LeakViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.tool = [[LeakShowTool alloc] init];
    self.tool.delegate = self;
}

- (void)show {
    NSLog(@"show");
}
@end
Copy the code

The results of this experiment are shown in the figure below:Circular references are also immediately generated when strong is used to modify proxy properties, so circular references are not exclusive to blocks.

The reason for the circular reference is shown below:

Weak: weak: weak: weak: weak

2.3 Block Causes circular reference

In the previous section, we showed the use of proxies to cause circular references. In this section, we return to the block and continue to explore why blocks cause circular references. First, we change the code to block form, as follows:

@interface ViewController(a)

@property (nonatomic.assign) int result;

@property (nonatomic.strong) void(^block)(void);

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.block = ^{
        NSLog(@"value = %d".self.result);
    };
}

@end
Copy the code

This code doesn’t need to be run to know that it will be referenced in a loop. To see why, let’s first look at the underlying form in which blocks exist.

To compile viewController.m from clang, run the following command:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m

After compiling, you get a viewController.cpp file, which opens as follows:

Because there’s so much code, I’m just going to take a screenshot of ViewDidLoad and block code. At the bottom, ViewDidLoad becomes _I_ViewController_viewDidLoad, as shown at the bottom of the diagram, and when setBlock: is called, an argument is passed, This parameter is of type __ViewController__viewDidLoad_block_impl_0, which is actually a structure, as shown at the top.

And in this structure, you see a member ViewController *self; The assignment to this member can be found in the __ViewController__viewDidLoad_block_impl_0 constructor, which is self passed in externally.

Combined with the OC code above, the end result is that self holds the block, and the block holds self, waiting to be released from each other, forming a circular reference, as shown below:

3, solve the block circular reference method

The previous section focused on the causes of circular references. This section continues to explore how to solve the problem of circular references in blocks. The key to solving the circular reference problem is to break the loop of references between objects. When one of the rings can be destroyed, the other objects in the ring can also be released, thus solving the problem. With this in mind, circular references can be addressed in several ways, each of which is described below.

3.1 _ _weak Resolve circular references

First, write the following code in LeakViewController:

@interface LeakViewController(a)

@property (nonatomic.assign) int result;

@property (nonatomic.strong) void(^block)(void);

@end

@implementation LeakViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@" Enter %@ page", [* *self** class]);
    self.block = ^{
        NSLog(@"value = %d".self.result);
    };
}

- (void)dealloc {
    NSLog(@"%@--%s"[self class], __FUNCTION__);
}

@end
Copy the code

The appellate code will definitely create a circular reference and print the following:

You can see that the page should be destroyed after exiting the page, but the dealloc method is not used. Change the code to look like this:

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@" Enter %@ page"[self class]);
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"value = %d", weakSelf.result);
    };
}
Copy the code

The result is as follows:

It is obvious that circular references work, as follows:

However, there is a problem in the above code. If delayed execution is added, weakSelf will be released in advance, and the code is as follows:

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view.

    NSLog(@" Enter %@ page"[self class]);
    self.result = 2;
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(0.0), ^ {NSLog(@"value = %d", weakSelf.result);
        });
    };

    self.block();
}
Copy the code

The result is as follows:

The last two times are the results after entering the page and exiting immediately. Obviously, weakSelf is nil at this time, so the printed result value = 0. Normally, this is fine, but if there is a real need to delay execution when exiting the page, it can be unpredictable. Therefore, if this is required, you can change the code to something like this:

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view.

    NSLog(@" Enter %@ page"[self class]);
    self.result = 2;
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(0.0), ^ {NSLog(@"value = %d", strongSelf.result);
        });
    };

    self.block();
}
Copy the code

StrongSelf makes a strong reference to weakSelf inside the block, weakSelf can be released normally, strongSelf is a local variable inside the block, so when the block is released, strongSelf will also be released. The print result is as follows:

3.2 Temporary variables and parameters

In the __weak and __strong ways above, strongSelf is really just a local variable, which avoids code execution exceptions in the code block after self is released. With this idea, we can break circular references by using local variables.

In OC, this can be done by __block, as follows:

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view.

    NSLog(@" Enter %@ page"[self class]);
    self.result = 2;
    __block LeakViewController *vc = self;
    self.block = ^{
        NSLog(@"value = %d", vc.result);
        vc = nil;
    };

    self.block();
}
Copy the code

The result is as follows:

This way you can also break the loop of circular references without worrying about premature release in the case of delayed execution, as long as the vc temporary variable is set to nil when the code block just wants to end.

However, this implementation is not elegant, because you have to manually set vc to nil every time, which increases the operating cost, and it is difficult to forget, which will cause circular references. Therefore, the following changes can be made:

@property (nonatomic.strong) void(^block)(LeakViewController *vc); // Modify the definition of block to pass self as an argument


- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view.

    NSLog(@" Enter %@ page"[self class]);
    self.result = 2;
    self.block = ^(LeakViewController *vc){
        NSLog(@"value = %d", vc.result);
    };

    self.block(self);
}
Copy the code

Because VC is an argument to block, vc is automatically set to nil when the block is finished executing, breaking the circular reference.

conclusion

This article explores the basics of blocks, including the classification of blocks and circular reference problems, and concludes with the following points:

  • There are three general types of blocks__NSGlobalBlock__,__NSMallocBlock__and__NSStackBlock__
    • Is that does not reference external local variables__NSGlobalBlock__, or for__NSMallocBlock__or__NSStackBlock__
    • __NSMallocBlock__and__NSStackBlock__The difference is whether it is a strong reference or copy, if it is__NSMallocBlock__, or for__NSStackBlock__
  • Improper use of blocks can result in circular references, in which references to objects form a closed loop and end up waiting for each other, with no one able to release them
  • There are ways to solve the problem of block circular references__weak and __strong,Temporary variables and parameters

The above is the basic summary of the block. How the block copies self and other variables is only briefly mentioned in this article. We will continue to explore it in the future and look forward to paying more attention to it.