iOS Block用法和實(shí)現(xiàn)原理
《Objective-C高級(jí)編程》是一本有趣又難懂的書(shū),全書(shū)就講了引用計(jì)數(shù)、Block、GCD三個(gè)概念,有趣是因?yàn)橹v原理、實(shí)現(xiàn)的部分是其它iOS專業(yè)書(shū)籍里少有的。然而每個(gè)章節(jié)不讀個(gè)三五遍還是比較難理解貫通的。本文針對(duì)其中的Block部分做些簡(jiǎn)單的筆記記錄,講述Block的用法和部分實(shí)現(xiàn)原理,詳細(xì)解說(shuō)從原書(shū)中尋。
Block概要
Block:帶有自動(dòng)變量的匿名函數(shù)。 匿名函數(shù):沒(méi)有函數(shù)名的函數(shù),一對(duì){}包裹的內(nèi)容是匿名函數(shù)的作用域。 自動(dòng)變量:棧上聲明的一個(gè)變量不是靜態(tài)變量和全局變量,是不可以在這個(gè)棧內(nèi)聲明的匿名函數(shù)中使用的,但在Block中卻可以。 雖然使用Block不用聲明類,但是Block提供了類似Objective-C的類一樣可以通過(guò)成員變量來(lái)保存作用域外變量值的方法,那些在Block的一對(duì){}里使用到但卻是在{}作用域以外聲明的變量,就是Block截獲的自動(dòng)變量。
Block常規(guī)概念
Block語(yǔ)法
Block表達(dá)式語(yǔ)法:
^ 返回值類型 (參數(shù)列表) {表達(dá)式}
例如:
^ int (int count) {
return count + 1;
};
其中,可省略部分有:
- 返回類型,例:
^ (int count) {
return count + 1;
};
- 參數(shù)列表為空,則可省略,例:
^ {
NSLog(@"No Parameter");
};
即最簡(jiǎn)模式語(yǔ)法為:
^ {表達(dá)式}
Block類型變量
聲明Block類型變量語(yǔ)法:
返回值類型 (^變量名)(參數(shù)列表) = Block表達(dá)式
例如,如下聲明了一個(gè)變量名為blk的Block:
int (^blk)(int) = ^(int count) {
return count + 1;
};
當(dāng)Block類型變量作為函數(shù)的參數(shù)時(shí),寫(xiě)作:
- (void)func:(int (^)(int))blk {
NSLog(@"Param:%@", blk);
}
借助typedef可簡(jiǎn)寫(xiě):
typedef int (^blk_k)(int);
- (void)func:(blk_k)blk {
NSLog(@"Param:%@", blk);
}
Block類型變量作返回值時(shí),寫(xiě)作:
- (int (^)(int))funcR {
return ^(int count) {
return count ++;
};
}
借助typedef簡(jiǎn)寫(xiě):
typedef int (^blk_k)(int);
- (blk_k)funcR {
return ^(int count) {
return count ++;
};
}
截獲自動(dòng)變量值
Block表達(dá)式可截獲所使用的自動(dòng)變量的值。 截獲:保存自動(dòng)變量的瞬間值。 因?yàn)槭恰八查g值”,所以聲明Block之后,即便在Block外修改自動(dòng)變量的值,也不會(huì)對(duì)Block內(nèi)截獲的自動(dòng)變量值產(chǎn)生影響。 例如:
int i = 10;
void (^blk)(void) = ^{
NSLog(@"In block, i = %d", i);
};
i = 20;//Block外修改變量i,也不影響B(tài)lock內(nèi)的自動(dòng)變量
blk();//i修改為20后才執(zhí)行,打印: In block, i = 10
NSLog(@"i = %d", i);//打印:i = 20
__block說(shuō)明符號(hào)
自動(dòng)變量截獲的值為Block聲明時(shí)刻的瞬間值,保存后就不能改寫(xiě)該值,如需對(duì)自動(dòng)變量進(jìn)行重新賦值,需要在變量聲明前附加__block說(shuō)明符,這時(shí)該變量稱為_(kāi)_block變量。 例如:
__block int i = 10;//i為_(kāi)_block變量,可在block中重新賦值
void (^blk)(void) = ^{
NSLog(@"In block, i = %d", i);
};
i = 20;
blk();//打印: In block, i = 20
NSLog(@"i = %d", i);//打?。篿 = 20
自動(dòng)變量值為一個(gè)對(duì)象情況
當(dāng)自動(dòng)變量為一個(gè)類的對(duì)象,且沒(méi)有使用__block修飾時(shí),雖然不可以在Block內(nèi)對(duì)該變量進(jìn)行重新賦值,但可以修改該對(duì)象的屬性。 如果該對(duì)象是個(gè)Mutable的對(duì)象,例如NSMutableArray,則還可以在Block內(nèi)對(duì)NSMutableArray進(jìn)行元素的增刪:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@"1", @"2",nil ];
NSLog(@"Array Count:%ld", array.count);//打印Array Count:2
void (^blk)(void) = ^{
[array removeObjectAtIndex:0];//Ok
//array = [NSNSMutableArray new];//沒(méi)有__block修飾,編譯失?。?};
blk();
NSLog(@"Array Count:%ld", array.count);//打印Array Count:1
Block實(shí)現(xiàn)原理
使用Clang
Block實(shí)際上是作為極普通的C語(yǔ)言源碼來(lái)處理的:含有Block語(yǔ)法的源碼首先被轉(zhuǎn)換成C語(yǔ)言編譯器能處理的源碼,再作為普通的C源代碼進(jìn)行編譯。 使用LLVM編譯器的clang命令可將含有Block的Objective-C代碼轉(zhuǎn)換成C++的源代碼,以探查其具體實(shí)現(xiàn)方式:
clang -rewrite-objc 源碼文件名
注:如果使用該命令報(bào)錯(cuò):'UIKit/UIKit.h' file not found,可參考《Objective-C編譯成C++代碼報(bào)錯(cuò)》解決。
Block結(jié)構(gòu)
使用Block的時(shí)候,編譯器對(duì)Block語(yǔ)法進(jìn)行了怎樣的轉(zhuǎn)換?
int main() {
int count = 10;
void (^ blk)() = ^(){
NSLog(@"In Block:%d", count);
};
blk();
}
如上所示的最簡(jiǎn)單的Block使用代碼,經(jīng)clang轉(zhuǎn)換后,可得到以下幾個(gè)部分(有代碼刪減和注釋添加):
static void __main_block_func_0(
struct __main_block_impl_0 *__cself) {
int count = __cself->count; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_vf2p_jz52yz7x4xtcx55yv0r0000gn_T_main_d2f8d2_mi_0,
count);
}
這是一個(gè)函數(shù)的實(shí)現(xiàn),對(duì)應(yīng)Block中{}內(nèi)的內(nèi)容,這些內(nèi)容被當(dāng)做了C語(yǔ)言函數(shù)來(lái)處理,函數(shù)參數(shù)中的__cself相當(dāng)于Objective-C中的self。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; //描述Block大小、版本等信息
int count;
//構(gòu)造函數(shù)函數(shù)
__main_block_impl_0(void *fp,
struct __main_block_desc_0 *desc,
int _count,
int flags=0) : count(_count) {
impl.isa = &_NSConcreteStackBlock; //在函數(shù)棧上聲明,則為_(kāi)NSConcreteStackBlock
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0即為main()函數(shù)棧上的Block結(jié)構(gòu)體,其中的__block_impl結(jié)構(gòu)體聲明如下:
struct __block_impl {
void *isa;//指明對(duì)象的Class
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl結(jié)構(gòu)體,即為Block的結(jié)構(gòu)體,可理解為Block的類結(jié)構(gòu)。 再看下main()函數(shù)翻譯的內(nèi)容:
int main() {
int count = 10;
void (* blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
去除掉復(fù)雜的類型轉(zhuǎn)化,可簡(jiǎn)寫(xiě)為:
int main() {
int count = 10;
sturct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0, //函數(shù)指針
&__main_block_desc_0_DATA)); //Block大小、版本等信息
(*blk->FuncPtr)(blk); //調(diào)用FuncPtr指向的函數(shù),并將blk自己作為參數(shù)傳入
}
由此,可以看出,Block也是Objective-C中的對(duì)象。 Block有三種類(即__block_impl的isa指針指向的值,isa說(shuō)明參考《Objective-C isa 指針 與 runtime 機(jī)制》),根據(jù)Block對(duì)象創(chuàng)建時(shí)所處數(shù)據(jù)區(qū)不同而進(jìn)行區(qū)別:
- _NSConcreteStackBlock:在棧上創(chuàng)建的Block對(duì)象
- _NSConcreteMallocBlock:在堆上創(chuàng)建的Block對(duì)象
- _NSConcreteGlobalBlock:全局?jǐn)?shù)據(jù)區(qū)的Block對(duì)象
如何截獲自動(dòng)變量
上部分介紹了Block的結(jié)構(gòu),和作為匿名函數(shù)的調(diào)用機(jī)制,那自動(dòng)變量截獲是發(fā)生在什么時(shí)候呢? 觀察上節(jié)代碼中__main_block_impl_0結(jié)構(gòu)體(main棧上Block的結(jié)構(gòu)體)的構(gòu)造函數(shù)可以看到,棧上的變量count以參數(shù)的形式傳入到了這個(gè)構(gòu)造函數(shù)中,此處即為變量的自動(dòng)截獲。 因此可以這樣理解:__block_impl結(jié)構(gòu)體已經(jīng)可以代表Block類了,但在棧上又聲明了__main_block_impl_0結(jié)構(gòu)體,對(duì)__block_impl進(jìn)行封裝后才來(lái)表示棧上的Block類,就是為了獲取Block中使用到的棧上聲明的變量(棧上沒(méi)在Block中使用的變量不會(huì)被捕獲),變量被保存在Block的結(jié)構(gòu)體實(shí)例中。 所以在blk()執(zhí)行之前,棧上簡(jiǎn)單數(shù)據(jù)類型的count無(wú)論發(fā)生什么變化,都不會(huì)影響到Block以參數(shù)形式傳入而捕獲的值。但這個(gè)變量是指向?qū)ο蟮闹羔槙r(shí),是可以修改這個(gè)對(duì)象的屬性的,只是不能為變量重新賦值。
Block的存儲(chǔ)域
上文已提到,根據(jù)Block創(chuàng)建的位置不同,Block有三種類型,創(chuàng)建的Block對(duì)象分別會(huì)存儲(chǔ)到棧、堆、全局?jǐn)?shù)據(jù)區(qū)域。
void (^blk)(void) = ^{
NSLog(@"Global Block");
};
int main() {
blk();
NSLog(@"%@",[blk class]);//打?。篲_NSGlobalBlock__
}
像上面代碼塊中的全局blk自然是存儲(chǔ)在全局?jǐn)?shù)據(jù)區(qū),但注意在函數(shù)棧上創(chuàng)建的blk,如果沒(méi)有截獲自動(dòng)變量,Block的結(jié)構(gòu)實(shí)例還是會(huì)被設(shè)置在程序的全局?jǐn)?shù)據(jù)區(qū),而非棧上:
int main() {
void (^blk)(void) = ^{//沒(méi)有截獲自動(dòng)變量的Block
NSLog(@"Stack Block");
};
blk();
NSLog(@"%@",[blk class]);//打印:__NSGlobalBlock__
int i = 1;
void (^captureBlk)(void) = ^{//截獲自動(dòng)變量i的Block
NSLog(@"Capture:%d", i);
};
captureBlk();
NSLog(@"%@",[captureBlk class]);//打?。篲_NSMallocBlock__
}
可以看到截獲了自動(dòng)變量的Block打印的類是NSGlobalBlock,表示存儲(chǔ)在全局?jǐn)?shù)據(jù)區(qū)。 但為什么捕獲自動(dòng)變量的Block打印的類卻是設(shè)置在堆上的NSMallocBlock,而非棧上的NSStackBlock?這個(gè)問(wèn)題稍后解釋。
Block復(fù)制
配置在棧上的Block,如果其所屬的棧作用域結(jié)束,該Block就會(huì)被廢棄,對(duì)于超出Block作用域仍需使用Block的情況,Block提供了將Block從棧上復(fù)制到堆上的方法來(lái)解決這種問(wèn)題,即便Block棧作用域已結(jié)束,但被拷貝到堆上的Block還可以繼續(xù)存在。 復(fù)制到堆上的Block,將_NSConcreteMallocBlock類對(duì)象寫(xiě)入Block結(jié)構(gòu)體實(shí)例的成員變量isa:
impl.isa = &_NSConcreteMallocBlock;
在ARC有效時(shí),大多數(shù)情況下編譯器會(huì)進(jìn)行判斷,自動(dòng)生成將Block從棧上復(fù)制到堆上的代碼(或者直接在堆上創(chuàng)建Block對(duì)象),以下幾種情況棧上的Block會(huì)自動(dòng)復(fù)制到堆上:
- 調(diào)用Block的copy方法
- 將Block作為函數(shù)返回值時(shí)(MRC時(shí)此條無(wú)效,需手動(dòng)調(diào)用copy)
- 將Block賦值給__strong修改的變量時(shí)(MRC時(shí)此條無(wú)效)
- 向Cocoa框架含有usingBlock的方法或者GCD的API傳遞Block參數(shù)時(shí)
其它時(shí)候向方法的參數(shù)中傳遞Block時(shí),需要手動(dòng)調(diào)用copy方法復(fù)制Block。 上一節(jié)的棧上截獲了自動(dòng)變量i的Block之所以在棧上創(chuàng)建,卻是NSMallocBlock_類,就是因?yàn)檫@個(gè)Block對(duì)象賦值給了__strong修飾的變量**captureBlk(_strong是ARC下對(duì)象的默認(rèn)修飾符)。 因?yàn)樯厦嫠臈l規(guī)則,在ARC下其實(shí)很少見(jiàn)到_NSConcreteStackBlock類的Block,大多數(shù)情況編譯器都保證了Block是在堆上創(chuàng)建的,如下代碼所示,僅最后一行代碼直接使用一個(gè)不賦值給變量的Block,它的類才是NSStackBlock:
int count = 0;
blk_t blk = ^(){
NSLog(@"In Stack:%d", count);
};
NSLog(@"blk's Class:%@", [blk class]);//打?。篵lk's Class:__NSMallocBlock__
NSLog(@"Global Block:%@", [^{NSLog(@"Global Block");} class]);//打?。篏lobal Block:__NSGlobalBlock__
NSLog(@"Copy Block:%@", [[^{NSLog(@"Copy Block:%d",count);} copy] class]);//打?。篊opy Block:__NSMallocBlock__
NSLog(@"Stack Block:%@", [^{NSLog(@"Stack Block:%d",count);} class]);//打?。篠tack Block:__NSStackBlock__
關(guān)于ARC下和MRC下Block自動(dòng)copy的區(qū)別,查看《Block 小測(cè)驗(yàn)》里幾道題目就能區(qū)分了。 另外,原書(shū)存在ARC和MRC混合講解、區(qū)分不明的情況,比如書(shū)中幾個(gè)使用到棧上對(duì)象導(dǎo)致Crash的例子是MRC條件下才會(huì)發(fā)生的,但書(shū)中沒(méi)做特殊說(shuō)明。
使用__block發(fā)生了什么
Block捕獲的自動(dòng)變量添加__block說(shuō)明符,就可在Block內(nèi)讀和寫(xiě)該變量,也可以在原來(lái)的棧上讀寫(xiě)該變量。 自動(dòng)變量的截獲保證了棧上的自動(dòng)變量被銷毀后,Block內(nèi)仍可使用該變量。 __block保證了棧上和Block內(nèi)(通常在堆上)可以訪問(wèn)和修改“同一個(gè)變量”,__block是如何實(shí)現(xiàn)這一功能的?
__block發(fā)揮作用的原理:將棧上用__block修飾的自動(dòng)變量封裝成一個(gè)結(jié)構(gòu)體,讓其在堆上創(chuàng)建,以方便從棧上或堆上訪問(wèn)和修改同一份數(shù)據(jù)。
驗(yàn)證過(guò)程: 現(xiàn)在對(duì)剛才的代碼段,加上__block說(shuō)明符,并在block內(nèi)外讀寫(xiě)變量count。
int main() {
__block int count = 10;
void (^ blk)() = ^(){
count = 20;
NSLog(@"In Block:%d", count);//打?。篒n Block:20
};
count ++;
NSLog(@"Out Block:%d", count);//打?。篛ut Block:11
blk();
}
將上面的代碼段clang,發(fā)現(xiàn)Block的結(jié)構(gòu)體__main_block_impl_0結(jié)構(gòu)如下所示:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_count_0 *count; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_count_0 *_count, int flags=0) : count(_count->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
最大的變化就是count變量不再是int類型了,count變成了一個(gè)指向__Block_byref_count_0結(jié)構(gòu)體的指針,__Block_byref_count_0結(jié)構(gòu)如下:
struct __Block_byref_count_0 {
void *__isa;
__Block_byref_count_0 *__forwarding;
int __flags;
int __size;
int count;
};
它保存了int count變量,還有一個(gè)指向__Block_byref_count_0實(shí)例的指針__forwarding,通過(guò)下面兩段代碼__forwarding指針的用法可以知道,該指針其實(shí)指向的是對(duì)象自身:
//Block的執(zhí)行函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_count_0 *count = __cself->count; // bound by ref
(count->__forwarding->count) = 20;//對(duì)應(yīng)count = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_vf2p_jz52yz7x4xtcx55yv0r0000gn_T_main_fafeeb_mi_0,
(count->__forwarding->count));
}
//main函數(shù)
int main() {
__attribute__((__blocks__(byref))) __Block_byref_count_0 count = {(void*)0,
(__Block_byref_count_0 *)&count, 0,
sizeof(__Block_byref_count_0), 10};
void (* blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_count_0 *)&count,
570425344));
(count.__forwarding->count) ++;//對(duì)應(yīng)count ++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_vf2p_jz52yz7x4xtcx55yv0r0000gn_T_main_fafeeb_mi_1,
(count.__forwarding->count));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
為什么要通過(guò)__forwarding指針完成對(duì)count變量的讀寫(xiě)修改? 為了保證無(wú)論是在棧上還是在堆上,都能通過(guò)都__forwarding指針找到在堆上創(chuàng)建的count這個(gè)__main_block_func_0結(jié)構(gòu)體,以完成對(duì)count->count(第一個(gè)count是__main_block_func_0對(duì)象,第二個(gè)count是int類型變量)的訪問(wèn)和修改。 示意圖如下: Block的循環(huán)引用
Block的循環(huán)引用原理和解決方法大家都比較熟悉,此處將結(jié)合上文的介紹,介紹一種不常用的解決Block循環(huán)引用的方法和一種借助Block參數(shù)解決該問(wèn)題的方法。 Block循環(huán)引用原因:一個(gè)對(duì)象A有Block類型的屬性,從而持有這個(gè)Block,如果Block的代碼塊中使用到這個(gè)對(duì)象A,或者僅僅是用用到A對(duì)象的屬性,會(huì)使Block也持有A對(duì)象,導(dǎo)致兩者互相持有,不能在作用域結(jié)束后正常釋放。 解決原理:對(duì)象A照常持有Block,但Block不能強(qiáng)引用持有對(duì)象A以打破循環(huán)。 解決方法: 方法一: 對(duì)Block內(nèi)要使用的對(duì)象A使用__weak進(jìn)行修飾,Block對(duì)對(duì)象A弱引用打破循環(huán)。
有三種常用形式:
- 使用__weak ClassName
__block XXViewController* weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf);
};
- 使用__weak typeof(self)
__weak typeof(self) weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf);
};
- Reactive Cocoa中的@weakify和@strongify
@weakify(self);
self.blk = ^{
@strongify(self);
NSLog(@"In Block : %@",self);
};
其原理參考《@weakify, @strongify》,自己簡(jiǎn)便實(shí)現(xiàn)參考《@weak - @strong 宏的實(shí)現(xiàn)》
方法二:對(duì)Block內(nèi)要使用的對(duì)象A使用__block進(jìn)行修飾,并在代碼塊內(nèi),使用完__block變量后將其設(shè)為nil,并且該block必須至少執(zhí)行一次。
__block XXController *blkSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",blkSelf);
};
注意上述代碼仍存在內(nèi)存泄露,因?yàn)椋?/p>
- XXController對(duì)象持有Block對(duì)象blk
- blk對(duì)象持有__block變量blkSelf
- __block變量blkSelf持有XXController對(duì)象
__block XXController *blkSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",blkSelf);
blkSelf = nil;//不能省略
};
self.blk();//該block必須執(zhí)行一次,否則還是內(nèi)存泄露
在block代碼塊內(nèi),使用完使用完__block變量后將其設(shè)為nil,并且該block必須至少執(zhí)行一次后,不存在內(nèi)存泄露,因?yàn)榇藭r(shí):
- XXController對(duì)象持有Block對(duì)象blk
- blk對(duì)象持有__block變量blkSelf(類型為編譯器創(chuàng)建的結(jié)構(gòu)體)
- __block變量blkSelf在執(zhí)行blk()之后被設(shè)置為nil(__block變量結(jié)構(gòu)體的__forwarding指針指向了nil),不再持有XXController對(duì)象,打破循環(huán)
第二種使用__block打破循環(huán)的方法,優(yōu)點(diǎn)是:
- 可通過(guò)__block變量動(dòng)態(tài)控制持有XXController對(duì)象的時(shí)間,運(yùn)行時(shí)決定是否將nil或其他變量賦值給__block變量
- 不能使用__weak的系統(tǒng)中,使用__unsafe_unretained來(lái)替代__weak打破循環(huán)可能有野指針問(wèn)題,使用__block則可避免該問(wèn)題
其缺點(diǎn)也明顯:
- 必須手動(dòng)保證__block變量最后設(shè)置為nil
- block必須執(zhí)行一次,否則__block不為nil循環(huán)應(yīng)用仍存在
因此,還是避免使用第二種不常用方式,直接使用__weak打破Block循環(huán)引用。 方法三:將在Block內(nèi)要使用到的對(duì)象(一般為self對(duì)象),以Block參數(shù)的形式傳入,Block就不會(huì)捕獲該對(duì)象,而將其作為參數(shù)使用,其生命周期系統(tǒng)的棧自動(dòng)管理,不造成內(nèi)存泄露。 即原來(lái)使用__weak的寫(xiě)法:
__weak typeof(self) weakSelf = self;
self.blk = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"Use Property:%@", strongSelf.name);
//……
};
self.blk();
改為Block傳參寫(xiě)法后:
self.blk = ^(UIViewController *vc) {
NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);
優(yōu)點(diǎn):
- 簡(jiǎn)化了兩行代碼,更優(yōu)雅
- 更明確的API設(shè)計(jì):告訴API使用者,該方法的Block直接使用傳進(jìn)來(lái)的參數(shù)對(duì)象,不會(huì)造成循環(huán)引用,不用調(diào)用者再使用weak避免循環(huán)
該種用法的詳細(xì)思路,和clang后的數(shù)據(jù)結(jié)構(gòu),可參考《Heap-Stack Dance》。