在閱讀這篇文章之前化借,首先思考如下問(wèn)題:
- 為什么 weak strong dance 能夠避免循環(huán)引用?
- 為什么不直接使用weak帜乞?
- 使用 weak strong dance 的正確姿勢(shì)司抱?
本文從 weak strong dance 的** 由來(lái) 用途 原理 擴(kuò)展** 逐步分析解答上述問(wèn)題,但不僅僅只是解答問(wèn)題黎烈。
由來(lái)
在iOS開(kāi)發(fā)中习柠,無(wú)論objective-c還是swift都是采用引用計(jì)數(shù)來(lái)管理內(nèi)存。而循環(huán)引用算是采用引用計(jì)數(shù)法管理內(nèi)存中比較棘手的一個(gè)問(wèn)題照棋。在MRC時(shí)代资溃,因?yàn)閷?duì)象的引用計(jì)數(shù)是由程序猿自己來(lái)控制,優(yōu)秀的程序員能夠自如的把控對(duì)象之間的持有關(guān)系烈炭;到了ARC和swift中溶锭,由于編譯器接手了內(nèi)存管理的工作,為了方便程序員控制“對(duì)象之間的持有關(guān)系”梳庆,蘋(píng)果于2011 WWDC Session #322中提出 weak strong dance ,官方示例代碼如下
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
- (void)loadView
{
[super loadView];
__weak TestViewController *wself = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
TestViewController *sself = wself;
[sself dismissModalViewControllerAnimated:YES];
}];
}
用途
在使用block時(shí)暖途,因?yàn)閎lock會(huì)捕獲外部變量卑惜,并將其拷貝到block中。ARC 下這里的變量如果是指針變量驻售,會(huì)將指針變量所指向的對(duì)象的引用計(jì)數(shù)加一露久。 因此如果不是對(duì)block有一定理解很容易發(fā)生循環(huán)引用,在這篇文章里有介紹會(huì)發(fā)生循環(huán)引用的情景欺栗。
在RAC及swift中毫痕,為了避免block帶來(lái)的循環(huán)引用,官方推薦 weak strong dance 迟几,即 在block外部聲明一個(gè)弱引用對(duì)象消请,在block內(nèi)部聲明一個(gè)局部變量強(qiáng)持有這個(gè)弱引用,通過(guò)使用新生成局部變量來(lái)避免循環(huán)引用类腮。
原理
說(shuō)到原理臊泰,得從block的內(nèi)部結(jié)構(gòu)說(shuō)起:
其中invoke指向block的實(shí)現(xiàn)代碼,variables保存block捕獲到的外部變量蚜枢。
現(xiàn)在來(lái)分析引言中的示例代碼:
- block 會(huì)將wself捕獲到variables中缸逃,因?yàn)槭莣eak修飾的,因此block不會(huì)對(duì)self進(jìn)行強(qiáng)引用厂抽;同時(shí) block 中的 invoke (函數(shù)指針)會(huì)指向 block 中的實(shí)現(xiàn)代碼需频。
- 在執(zhí)行block時(shí)(即調(diào)用invoke),
TestViewController *sself = wself;
才會(huì)執(zhí)行,如果執(zhí)行這行代碼時(shí)self還未釋放筷凤,那么這里會(huì)將TestViewController實(shí)例的引用計(jì)數(shù)加一昭殉,防止在調(diào)用self期間self已經(jīng)被釋放。當(dāng)這個(gè)方法執(zhí)行完藐守,ARC會(huì)自動(dòng)將引用計(jì)數(shù)減一挪丢。 - 如果在執(zhí)行block時(shí),self已經(jīng)釋放吗伤,即wself被置為nil吃靠。那么
TestViewController *sself = wself;
執(zhí)行時(shí)sself得到的也是一個(gè)nil,因此[sself dismissModalViewControllerAnimated:YES];
將不會(huì)被執(zhí)行足淆。 - 如果所有時(shí)候都和3中一樣只是“不執(zhí)行”巢块,那該有多好。但是結(jié)果往往不如人意巧号。不信族奢? 測(cè)試代碼如下:
///WeakTestObject.h
typedef void(^test_block)(void);
@interface WeakTestObject : NSObject
/**
* <#summary#>
*/
@property (copy,nonatomic) test_block block;
@end
///ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
WeakTestObject *obj = [[WeakTestObject alloc]init];
obj.block = ^{
NSLog(@"execute block in obj");
};
__weak __typeof(obj) wObj = obj;
test_block VcBlock = ^{
WeakTestObject *sObj = wObj;
sObj.block();
};
obj = nil;
VcBlock();
}
當(dāng) VcBlock
在執(zhí)行之前,obj已經(jīng)釋放丹鸿,導(dǎo)致執(zhí)行 VcBlock
過(guò)程中 sObj
以及 sObj.block
均為nil越走。程序進(jìn)而crash在 sObj.block();
這里。 真實(shí)情況往往比這里的模擬代碼復(fù)雜很多,可能會(huì)經(jīng)過(guò)幾次時(shí)間和空間的跨度廊敌;那么如何避免這種crash呢铜跑?
兩種處理:
- 1 、對(duì)
sObj.block
進(jìn)行判斷
test_block block = ^{
WeakTestObject *sObj = wObj;
if (sObj.block) {
sObj.block();
}
};
- 2骡澈、 對(duì)
sObj
進(jìn)行判斷
test_block block = ^{
WeakTestObject *sObj = wObj;
if (sObj) {
sObj.block();
}
};
顯然第二種處理更優(yōu)锅纺。首先餓哦們沒(méi)有必要對(duì)sObj的每一個(gè)舒心進(jìn)行判斷,其實(shí) 在使用sObj 時(shí) 肋殴,往往也不是僅僅執(zhí)行它的一個(gè)block屬性囤锉,而且會(huì)涉及到block嵌套或其他各種坑爹情況,其次根據(jù)接口封閉原則我們也不應(yīng)該過(guò)多去關(guān)心類的實(shí)現(xiàn)护锤。
最終 weak strong dance 的正確姿勢(shì)如下:
__weak __typeof(obj) wObj = obj;
test_block block = ^{
WeakTestObject *sObj = wObj;
if (sObj) {
/// do ...
}
};
擴(kuò)展
1. RAC中的宏
因?yàn)镽AC中大量使用block語(yǔ)言官地,為了方便開(kāi)發(fā)者RAC中定義了一對(duì)宏 @weakify() @strongify()
,對(duì)于這對(duì)宏的具體分析可閱讀哀殿的 這篇文章 烙懦,文中提到“Xcode 丟失了錯(cuò)誤提示的能力”這一問(wèn)題
另外YYKit中也定義了類似的宏驱入,同時(shí)避免了上述問(wèn)題,如下
#ifndef weakify
#if DEBUG
#if __has_feature(objc_arc)
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
#endif
#else
#if __has_feature(objc_arc)
#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
#endif
#endif
#endif
#ifndef strongify
#if DEBUG
#if __has_feature(objc_arc)
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
#endif
#else
#if __has_feature(objc_arc)
#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
#endif
#endif
#endif
這里補(bǔ)充另一個(gè)坑(請(qǐng)注意如下兩種調(diào)用的區(qū)別)
//1
[self.viewModel.resignSubject subscribeNext:^(id x) {
@strongify(self)
[self.keyBoard keyboardDown];
}];
//2
[self.viewModel.resignSubject subscribeNext:^(id x) {
@strongify(self)
[_keyBoard keyboardDown];
}];
在 1 中 self.keyBoard
中的self其實(shí)是被重定義的局部的“self”, 而我們通過(guò) _keyBoard
調(diào)用的話,表面上雖然看起來(lái)連self都沒(méi)有調(diào)用氯析,更不會(huì)有什么問(wèn)題了沧侥。但,第二種寫(xiě)法其實(shí)是存在很大隱患的魄鸦,系統(tǒng)在“尋找” _keyBoard
這個(gè)實(shí)例對(duì)象時(shí),是通過(guò)對(duì) self 指針進(jìn)行地址偏移得到的癣朗,在這里編譯器可不會(huì)對(duì)這個(gè)self進(jìn)行宏替換拾因。
在RAC源碼中還有對(duì)另一個(gè)宏 @unsafeify()
的使用
RACCompoundDisposable *selfDisposable = self.disposable;
[selfDisposable addDisposable:otherDisposable];
@unsafeify(otherDisposable);
// If this subscription terminates, purge its disposable to avoid unbounded
// memory growth.
[otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{
@strongify(otherDisposable);
[selfDisposable removeDisposable:otherDisposable];
}]];
@unsafeify()
就是 __unsafe_unretained
在RAC中的宏,但是這種用法即使在RAC源碼中出現(xiàn)的都極少旷余,不過(guò) __unsafe_unretained
對(duì)比 __weak
來(lái)說(shuō)在性能會(huì)比較好绢记。
2. 接口設(shè)計(jì)原則
首先,并不是涉及到block引用外部對(duì)象的問(wèn)題都會(huì)帶來(lái)循環(huán)引用正卧;其次蠢熄,如果是我們自己設(shè)計(jì)一個(gè)類的時(shí)候,應(yīng)該盡量的避免可能產(chǎn)生循環(huán)引用的問(wèn)題(例如執(zhí)行完block后將其置nil)炉旷,如果實(shí)在無(wú)法避免應(yīng)該在接口里詳細(xì)說(shuō)明签孔。
例如:蘋(píng)果框架中UIView封裝的 animateWithDuration
方法、GCD等都
不會(huì)帶來(lái)循環(huán)引用(注:NSTimer方法可能會(huì)帶來(lái)循環(huán)引用); 還有一些有名的三方框架例如 Masonry 也不會(huì)產(chǎn)生循環(huán)引用窘行。
3. swift 中的 weak strong dance
testFunc(closure:{ [weak self] in
if let strongSelf = self {
// do something
print(strongSelf)
}
})
testFunc(closure:{ [weak self] in
guard let strongSelf = self else {return}
///do something
print(strongSelf)
})
上面是根據(jù)可選綁定方式得來(lái)的較常規(guī)的寫(xiě)法饥追,還有如下這種方式,可避免另外顯示生成strongSelf
testFunc(closure:{ [weak self] in
withExtendedLifetime(self, {
print(self ?? 0)
})
})
只是經(jīng) withExtendedLifetime
處理后的self 變?yōu)榱艘粋€(gè)可選類型罐盔。