前言
在OC中,我們經(jīng)常會(huì)遇到一個(gè)東西叫循環(huán)引用,毫無疑問,循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄漏,嚴(yán)重的時(shí)候,導(dǎo)致應(yīng)用程序奔潰也是可能的。我們經(jīng)常遇到的循環(huán)引用就是Block(或者delegate)所引起的,而解決的方式也是老生常談的使用weak來弱引用被引用的對(duì)象,打破循環(huán),這樣就可以避免循環(huán)引用這個(gè)問題。
但是,如果你稍微不慎,有時(shí)候使用weak也會(huì)導(dǎo)致應(yīng)用程序奔潰,造成難以挽回的后果。這篇文章就是簡(jiǎn)要說明下,如何正確地使用weak,以及有時(shí)候需要結(jié)合strong來避免循環(huán)引用的內(nèi)存泄漏。
Block循環(huán)引用
一個(gè)對(duì)象持有一個(gè)Block,這個(gè)Block中又引用了這個(gè)對(duì)象,這就是循環(huán)引用。最常見最簡(jiǎn)單的就是持有當(dāng)前self,這也是在開發(fā)中經(jīng)常遇到的情況。在SecondViewController.m中,比如:
#import "SecondViewController.h"
#import "MyObject.h"
@interface SecondViewController ()
@property (nonatomic, strong) MyObject *obj;
@property (nonatomic, copy) NSString *name;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Second VC";
self.name = @"Alice";
self.obj = [[MyObject alloc] init];
[self.obj start:^{
NSLog(@"name: %@", self.name);
}];
}
- (void)dealloc
{
NSLog(@"OOPS! ?????? %s", __PRETTY_FUNCTION__);
}
@end
在MyObject.h中
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^ExecuteBlock)(void);
@interface MyObject : NSObject
- (void)start:(ExecuteBlock)block;
@end
NS_ASSUME_NONNULL_END
MyObject.m中
#import "MyObject.h"
@interface MyObject()
@property (nonatomic, copy) ExecuteBlock myBlock;
@end
@implementation MyObject
- (instancetype)init
{
self = [super init];
if (self) {
[self initData];
}
return self;
}
- (void)initData
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.myBlock) {
self.myBlock();
}
});
}
- (void)start:(ExecuteBlock)block
{
self.myBlock = block;
}
@end
顯然,self持有obj,obj持有block (start方法的參數(shù)block), block又持有self(name是當(dāng)前self的一個(gè)屬性),因此造成了顯而易見的循環(huán)引用。
這種處理起來也是十分簡(jiǎn)單,一個(gè)weak就可以搞定:
self.obj = [[MyObject alloc] init];
__weak typeof(self) weakSelf = self;
[self.obj start:^{
NSLog(@"name: %@", weakSelf.name);
}];
在這里,我們這樣處理,有什么問題嗎?答案是沒有任何問題。
接著,我們把name換成成員變量,即:
@interface SecondViewController ()
{
NSString *name;
}
@property (nonatomic, strong) MyObject *obj;
@end
......
self.obj = [[MyObject alloc] init];
__weak typeof(self) weakSelf = self;
[self.obj start:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"name: %@", strongSelf->name);
}];
......
注意,由于name是成員變量,不能使用weakSelf來引用name,因?yàn)樗且粋€(gè)弱指針。因此這里必須對(duì)weakSelf做一次強(qiáng)引用,即使用strongSelf來引用name。
想一下,當(dāng)用戶進(jìn)入頁面,在10s內(nèi)返回上一級(jí)頁面,待block被執(zhí)行時(shí),會(huì)發(fā)生什么?應(yīng)用程序奔潰?。?!
Crash分析
奇怪!為什么會(huì)發(fā)生這個(gè)問題呢?感覺沒問題了呀,使用weakSelf避免循環(huán)引用,使用strongSelf來引用成員變量,怎么就奔潰了呢?當(dāng)name是屬性時(shí),即weakSelf.name時(shí),并不會(huì)有任何問題,就只是將name由屬性變成成員變量就不行了?
實(shí)際上,只要理解OC中的消息發(fā)送機(jī)制,你就基本能夠知道上面所說的奔潰原因了。在OC中,對(duì)nil發(fā)送消息是“合法”的,因此,當(dāng)使用點(diǎn)語法來訪問時(shí),實(shí)際上是訪問屬性(這里是name)的getter方法,因此不會(huì)發(fā)生奔潰。
但是,如果是獲取成員變量的話,就不是方法了,而是通過self指針直接訪問其內(nèi)部的成員變量的內(nèi)存地址,此時(shí),當(dāng)頁面已經(jīng)釋放時(shí),self已經(jīng)不存在,strongSelf即為nil,可想而知,對(duì)一個(gè)不存在的“對(duì)象”,去訪問所謂的“成員變量”,即nil -> name,奔潰就是在意料之中了。
Crash解決
一 盡量使用屬性
這種方式其實(shí)本質(zhì)上就是基于OC的消息機(jī)制,對(duì)nil發(fā)送消息是允許的。這樣即使self被銷毀,也不會(huì)存在任何的問題,因?yàn)槟阃ㄟ^strongSelf.name調(diào)用的是方法,OC的消息機(jī)制允許你這樣做。雖然有時(shí)候我們出于性能考慮,會(huì)直接使用成員變量進(jìn)行獲取,因?yàn)檎{(diào)用方法是有一定代價(jià)的,但是,在大多數(shù)情況下,這樣的帶來的性能考量還是可以接受的,可忽略的。
因此,要絕對(duì)安全,在block內(nèi)部使用屬性的方式獲取,是一種可行的有效的方式。
二 依然使用成員變量,對(duì)weakSelf做安全檢查
如果你說我就是要使用成員變量來使性能達(dá)到最優(yōu)解,那也無可厚非,這種也是我們開發(fā)中很有必要的一種應(yīng)該必備的思想,但是,你要足夠小心,來避免奔潰的發(fā)生。解決方式就是對(duì)weakSelf做安全檢查,如:
self.obj = [[MyObject alloc] init];
__weak typeof(self) weakSelf = self;
[self.obj start:^{
if (!weakSelf) return; // Security-check here.
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"name: %@", strongSelf->name);
}];
總結(jié)
在開發(fā)中,一定要避免循環(huán)引用、代理所引起的內(nèi)存泄漏(memory leak),當(dāng)然,相信對(duì)于大多數(shù)開發(fā)者來說,這種基本不需要思考,都可以習(xí)以為常的寫上相應(yīng)的關(guān)鍵字來或者中間層來避免這個(gè)問題,但是估計(jì)也有不少人會(huì)出現(xiàn)本文所說的粗心大意,導(dǎo)致線上出現(xiàn)奔潰的問題。weak并不是在所有遇到block的情況都需要使用的,比如系統(tǒng)提供的動(dòng)畫API:
[UIView animateWithDuration:1.0 animations:^{
NSLog(@"name: %@", self->name);
}];
過度的不必要的weak使用會(huì)使代碼變得冗余并且產(chǎn)生不必要的性能問題!