【使用 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;
}
};