使用 Heap-Stack Dance 替代 Weak-Strong Dance铜犬,優(yōu)雅避開循環(huán)引用

【使用 Heap-Stack Dance 替代 Weak-Strong Dance,優(yōu)雅避開循環(huán)引用】Weak-Strong Dance這一最佳實(shí)踐的原理已經(jīng)被講爛了,開發(fā)者對(duì)該寫法已經(jīng)爛熟于心播聪。有相當(dāng)一部分開發(fā)者是不理解 Weak-Strong Dance 的原理,但卻寫得很溜布隔,即使沒必要加 strongSelf 的場(chǎng)景下也會(huì)添加上 strongSelf离陶。沒錯(cuò),這樣做衅檀,總是沒錯(cuò)招刨。

有沒有想過(guò)從API層面簡(jiǎn)化一下?

介紹下我的做法:

為 block 多加一個(gè)參數(shù)哀军,也就是 self 所屬類型的參數(shù)沉眶,那么在 block 內(nèi)部打却,該參數(shù)就會(huì)和 strongSelf 的效果一致谎倔。同時(shí)你也可以不寫 weakSelf,直接使用使用該參數(shù)(作用等同于直接使用 strongSelf )片习。這樣就達(dá)到了:“多加一個(gè)參數(shù),省掉兩行代碼”的效果毯侦。原理就是利用了“參數(shù)”的特性:參數(shù)是存放在棧中的(或寄存器中),系統(tǒng)負(fù)責(zé)回收侈离,開發(fā)者無(wú)需關(guān)心试幽。因?yàn)榻鉀Q問(wèn)題的思路是:將 block 會(huì)捕獲變量到堆上的問(wèn)題卦碾,化解為了:變量會(huì)被分配到棧(或寄存器中)上铺坞,所以我把種做法起名叫 Heap-Stack Dance 。

具體用法示例如下:
(詳見倉(cāng)庫(kù)中的Demo---文件夾叫做:weak-strong-drance-demo )


#import "Foo.h"

typedef void (^Completion)(Foo *foo);

@interface Foo ()

@property (nonatomic, copy) Completion completion1;
@property (nonatomic, copy) Completion completion2;

@end

@implementation Foo

- (instancetype)init {
   if (!(self = [super init])) {
       return nil;
   }
   __weak typeof(self) weakSelf = self;
   self.completion1 = ^(Foo *foo) {
       NSLog(@"completion1");
   };
   self.completion2 = ^(Foo *foo) {
       __strong typeof(self) strongSelf = weakSelf;
       NSLog(@"completion2");
       NSUInteger delaySeconds = 2;
       dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC));
       dispatch_after(when, dispatch_get_main_queue(), ^{
           NSLog(@"兩秒鐘后");
           foo.completion1(foo);//foo等價(jià)于strongSelf
       });
   };
   self.completion2(self);
   return self;
}

- (void)dealloc {
   NSLog(@"dealloc");
}

@end


@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   __autoreleasing Foo *foo = [Foo new];
}

@end

打印如下:

completion2
兩秒鐘后
completion1
dealloc

舉一個(gè)實(shí)際開發(fā)中的例子:

如果我們?yōu)閁IViewController添加了一個(gè)屬性洲胖,叫做viewDidLoadBlock济榨,讓用戶來(lái)進(jìn)行一些UI設(shè)置。

具體的做法如下:

@property (nonatomic, copy) CYLViewDidLoadBlock viewDidLoadBlock;

- (void)viewDidLoad {
   [super viewDidLoad];
   //...
   !self.viewDidLoadBlock ?: self.viewDidLoadBlock(self);
}

那么可以想象block中必然是要使用到viewController本身绿映,為了避免循環(huán)引用擒滑,之前我們不得不這樣做:

簡(jiǎn)化前:

__weak typeof(controller) weakController = conversationController;
[conversationController setViewDidLoadBlock:^{
   [weakController.navigationItem setTitle:@"XXX"];
}];

如果借助這種做法,簡(jiǎn)化后:

   [conversationViewController setViewDidLoadBlock:^(LCCKBaseViewController *viewController) {
       viewController.navigationItem.title = @"XXX";
   }];

這種可能優(yōu)勢(shì)不太明顯叉弦,畢竟編譯器都能看出來(lái)丐一,會(huì)報(bào)警告。但如果遇到了那種很難看出會(huì)造成循環(huán)引用的情景下淹冰,優(yōu)勢(shì)就顯現(xiàn)出來(lái)了库车。
尤其是在公開的 API 中,無(wú)法獲知 block 是否被 self 持有的樱拴,如果在 block 中加增一個(gè) self 類型的參數(shù)柠衍,因?yàn)?block 內(nèi)部已經(jīng)提供了 weakSelf 或者是 strongSelf 的替代者,那么調(diào)用者就可以在不使用 Weak-Strong Dance 的情況下避免循環(huán)引用晶乔。

下面這個(gè)語(yǔ)句珍坊,編譯器不會(huì)報(bào)警告,你能看出來(lái)有循環(huán)應(yīng)用嗎瘪弓?

比如我們?yōu)?UIViewController 添加了一個(gè)方法垫蛆,這個(gè)方法主要作用就是配置下 navigationBar 右上角的 item 樣式以及點(diǎn)擊事件:

   [aConversationController configureBarButtonItemStyle:LCCKBarButtonItemStyleGroupProfile
                                                 action:^(__kindof LCCKBaseViewController *viewController, UIBarButtonItem *sender, UIEvent *event) {                                                      [aConversationController.navigationController pushViewController:[UIViewController new] animated:YES];
                                                 }];

實(shí)際上你必須點(diǎn)擊進(jìn)去看一下該 API 的實(shí)現(xiàn),你才能發(fā)現(xiàn)原來(lái) aConversationController 持有了 action 這個(gè) block腺怯,而在這種用法中 block 又持有了 aConversationController 袱饭,所以這種情況是有循環(huán)引用的。

可以看下上述方法的具體的實(shí)現(xiàn):

- (void)configureBarButtonItemStyle:(LCCKBarButtonItemStyle)style action:(LCCKBarButtonItemActionBlock)action {
   NSString *icon;
   switch (style) {
       case LCCKBarButtonItemStyleSetting: {
           icon = @"barbuttonicon_set";
           break;
       }
       case LCCKBarButtonItemStyleMore: {
           icon = @"barbuttonicon_more";
           break;
       }
       case LCCKBarButtonItemStyleAdd: {
           icon = @"barbuttonicon_add";
           break;
       }
       case LCCKBarButtonItemStyleAddFriends:
           icon = @"barbuttonicon_addfriends";
           break;
       case LCCKBarButtonItemStyleSingleProfile:
           icon = @"barbuttonicon_InfoSingle";
           break;
       case LCCKBarButtonItemStyleGroupProfile:
           icon = @"barbuttonicon_InfoMulti";
           break;
       case LCCKBarButtonItemStyleShare:
           icon = @"barbuttonicon_Operate";
           break;
   }
   self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage lcck_imageNamed:icon bundleName:@"BarButtonIcon" bundleForClass:[self class]] style:UIBarButtonItemStylePlain target:self action:@selector(clickedBarButtonItemAction:event:)];
   self.barButtonItemAction = action;
}

- (void)clickedBarButtonItemAction:(UIBarButtonItem *)sender event:(UIEvent *)event {
   if (self.barButtonItemAction) {
       self.barButtonItemAction(self, sender, event);
   }
}

必須讓調(diào)用者理解了內(nèi)部實(shí)現(xiàn)呛占,才能用得好的API虑乖,不是一個(gè)好的API設(shè)計(jì)。

能不能在API層面就避免晾虑?增加一個(gè)self類型的參數(shù)就好了:

           [aConversationController configureBarButtonItemStyle:LCCKBarButtonItemStyleGroupProfile
                                                         action:^(__kindof LCCKBaseViewController *viewController, UIBarButtonItem *sender, UIEvent *event) {
                                                             [viewController.navigationController pushViewController:[UIViewController new] animated:YES];
                                                         }];

各位如果覺得好用疹味,可以到你的項(xiàng)目中使用 Heap-Stack Dance 替代 Weak-Strong Dance,重構(gòu)一些代碼帜篇。

這里還有另外一種方法來(lái)證明 self 做參數(shù)傳進(jìn) block 不會(huì)被 Block 捕獲:

用 clang 對(duì) Foo.m 文件轉(zhuǎn)成c/c++代碼:

clang -rewrite-objc Foo.m -Wno-deprecated-declarations -fobjc-arc

比如如下代碼:

   int tmpTarget;
   self.completion1 = ^(Foo *foo) {
       tmpTarget;
       NSLog(@"completion1");
   };
   self.completion1(self);

可以看到 Block 只會(huì)對(duì)傳入的 tmpTarget 引用糙捺,self 不會(huì)捕獲:

struct __Foo__init_block_impl_0 {
 struct __block_impl impl;
 struct __Foo__init_block_desc_0* Desc;
 int tmpTarget;
 __Foo__init_block_impl_0(void *fp, struct __Foo__init_block_desc_0 *desc, int _tmpTarget, int flags=0) : tmpTarget(_tmpTarget) {
   impl.isa = &_NSConcreteStackBlock;
   impl.Flags = flags;
   impl.FuncPtr = fp;
   Desc = desc;
 }
};

而如果是如下代碼 self 就會(huì)被捕獲:

int tmpTarget;
   self.completion1 = ^(Foo *foo) {
       tmpTarget;
       _b;
       NSLog(@"completion1");
   };
   self.completion1(self);
struct __Foo__init_block_impl_0 {
 struct __block_impl impl;
 struct __Foo__init_block_desc_0* Desc;
 int tmpTarget;
 Foo *__strong self;
 __Foo__init_block_impl_0(void *fp, struct __Foo__init_block_desc_0 *desc, int _tmpTarget, Foo *__strong _self, int flags=0) : tmpTarget(_tmpTarget), self(_self) {
   impl.isa = &_NSConcreteStackBlock;
   impl.Flags = flags;
   impl.FuncPtr = fp;
   Desc = desc;
 }
};

《后續(xù)更新會(huì)放在這里》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笙隙,一起剝皮案震驚了整個(gè)濱河市洪灯,隨后出現(xiàn)的幾起案子竟痰,更是在濱河造成了極大的恐慌,老刑警劉巖铅檩,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昧旨,死亡現(xiàn)場(chǎng)離奇詭異祥得,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)粘拾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門缰雇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)追驴,“玉大人,你說(shuō)我怎么就攤上這事殿雪。” “怎么了爸业?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵扯旷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我毯炮,道長(zhǎng)耸黑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任为迈,我火速辦了婚禮曲尸,結(jié)果婚禮上男翰,老公的妹妹穿的比我還像新娘。我一直安慰自己蛾绎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布鹏倘。 她就那樣靜靜地躺著纤泵,像睡著了一般镜粤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上公荧,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天循狰,我揣著相機(jī)與錄音窟社,去河邊找鬼灿里。 笑死昧识,一個(gè)胖子當(dāng)著我的面吹牛盗扒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甸祭,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼池户,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了校焦?” 一聲冷哼從身側(cè)響起统倒,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤房匆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后井氢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡花竞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年约急,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烤宙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俭嘁。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拐云,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膳帕,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布危彩,位于F島的核電站汤徽,受9級(jí)特大地震影響灸撰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浮毯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一债蓝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧器虾,春花似錦、人聲如沸兆沙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)库正。三九已至厘唾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抚垃,已是汗流浹背趟大。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工逊朽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曲伊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓岛蚤,卻偏偏與公主長(zhǎng)得像婿屹,于是被迫代替她去往敵國(guó)和親推溃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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