你真的懂 weak strong dance 嗎因俐?

在閱讀這篇文章之前化借,首先思考如下問(wèn)題:

  1. 為什么 weak strong dance 能夠避免循環(huán)引用?
  2. 為什么不直接使用weak帜乞?
  3. 使用 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ō)起:


block內(nèi)部結(jié)構(gòu)

其中invoke指向block的實(shí)現(xiàn)代碼,variables保存block捕獲到的外部變量蚜枢。

現(xiàn)在來(lái)分析引言中的示例代碼:

  1. block 會(huì)將wself捕獲到variables中缸逃,因?yàn)槭莣eak修飾的,因此block不會(huì)對(duì)self進(jìn)行強(qiáng)引用厂抽;同時(shí) block 中的 invoke (函數(shù)指針)會(huì)指向 block 中的實(shí)現(xiàn)代碼需频。
  2. 在執(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ù)減一挪丢。
  3. 如果在執(zhí)行block時(shí),self已經(jīng)釋放吗伤,即wself被置為nil吃靠。那么 TestViewController *sself = wself; 執(zhí)行時(shí)sself得到的也是一個(gè)nil,因此 [sself dismissModalViewControllerAnimated:YES]; 將不會(huì)被執(zhí)行足淆。
  4. 如果所有時(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è)可選類型罐盔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末但绕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捏顺,老刑警劉巖六孵,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異幅骄,居然都是意外死亡劫窒,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)昌执,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)烛亦,“玉大人,你說(shuō)我怎么就攤上這事懂拾∶呵荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵岖赋,是天一觀的道長(zhǎng)檬果。 經(jīng)常有香客問(wèn)我,道長(zhǎng)唐断,這世上最難降的妖魔是什么选脊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮脸甘,結(jié)果婚禮上恳啥,老公的妹妹穿的比我還像新娘。我一直安慰自己丹诀,他們只是感情好钝的,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著铆遭,像睡著了一般硝桩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枚荣,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天碗脊,我揣著相機(jī)與錄音,去河邊找鬼橄妆。 笑死衙伶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的害碾。 我是一名探鬼主播痕支,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蛮原!你這毒婦竟也來(lái)了卧须?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎花嘶,沒(méi)想到半個(gè)月后笋籽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡椭员,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年车海,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隘击。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侍芝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出埋同,到底是詐尸還是另有隱情州叠,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布凶赁,位于F島的核電站咧栗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏虱肄。R本人自食惡果不足惜致板,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咏窿。 院中可真熱鬧斟或,春花似錦、人聲如沸集嵌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纸淮。三九已至,卻和暖如春亚享,著一層夾襖步出監(jiān)牢的瞬間咽块,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工欺税, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侈沪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓晚凿,卻偏偏與公主長(zhǎng)得像亭罪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歼秽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容