iOS開發(fā)讀書筆記:Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理-上篇(自動引用計數(shù))
iOS開發(fā)讀書筆記:Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理-中篇(Blocks)
iOS開發(fā)讀書筆記:Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理-下篇(GCD)
閱讀完Block此篇后洗鸵,可以與iOS開發(fā)經(jīng)驗(25)-Block一塊閱讀,主要是可以加深對__forwarding的理解磷籍。
目錄
- 2.1 Blocks概要
- 2.1.1 什么是Blocks
- 2.2 Blocks模式
- 2.2.1 Blocks語法
- 2.2.2 Blocks類型變量
- 2.2.3 截獲自動變量值
- 2.2.4 __block說明符
- 2.2.5 截獲的自動變量
- 2.3 Blocks的實現(xiàn)
- 2.3.1 Block的實質(zhì)
- 2.3.2 截獲自動變量值
- 2.3.3 __block說明符
- 2.3.4 Block存儲域
- 2.3.5 __block變量存儲域
- 2.3.6 截獲對象
- 2.3.7 __block變量和對象
- 2.3.8 Block循環(huán)引用
- 2.3.9 copy/release
2.1 Blocks概要
2.1.1 什么是Blocks
Blocks是C語言的擴充功能藏畅〉甓粒可以用一句話來表示Blocks的擴充功能:帶有自動變量(局部變量)的匿名函數(shù)映胁。
顧名思義驻民,所謂匿名函數(shù)就是不帶有名稱的函數(shù)绽左。
C語言的標準函數(shù)如下:
int func(int count);//聲明函數(shù)
int result = func(10);//調(diào)用函數(shù)
如果像下面這樣悼嫉,使用函數(shù)指針來代替直接調(diào)用函數(shù),必須使用該函數(shù)的名稱func拼窥。
int result = (*funcptr)(10);
這樣以來戏蔑,函數(shù)func的地址就能賦值給函數(shù)指針類型變量funcptr中了。
但其實使用函數(shù)指針也仍然需要知道函數(shù)名稱鲁纠。若不使用想賦值的函數(shù)的名稱总棵,就無法取得該函數(shù)的地址。
int (*funcptr)(int) = &func;
int result = (*funcptr)(10);
通過Blocks改含,源代碼中就能夠使用匿名函數(shù)情龄,而不帶名稱的函數(shù)。
到這里我們知道了"帶有自動變量值的匿名函數(shù)"中"匿名函數(shù)"的概念捍壤。那么“帶有自動變量值”究竟是什么呢骤视?
首先回顧下函數(shù)中可能使用的變量:
- 自定變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
雖然這些變量的作用域不同,但在整個程序當中鹃觉,一個變量總保持在一個內(nèi)存區(qū)域专酗。
另外,“帶有自動變量值的匿名函數(shù)”這一概念并不僅指Blocks盗扇,它還存在于其他許多程序語言中祷肯。在計算機科學中,此概念也稱為閉包粱玲。
2.2 Blocks模式
2.2.1 Blocks語法
與一般的函數(shù)定義相比躬柬,僅有兩點不同
- 沒有函數(shù)名
- 帶有“^”記號(插入記號):因為OS X拜轨、iOS應(yīng)用程序的源代碼中將大量使用Block抽减,所以插入該記號便于查找。
以下為Block語法的BN范式
^ 返回值類型 參數(shù)列表 表達式
^ int (int count) {
return count + 1;
}
該源代碼可省略胃如下形式
^ {
return count + 1;
}
2.2.2 Blocks類型變量
在Block語法下橄碾,可將Block語法賦值給聲明為Block類型的變量中卵沉。既源代碼中一旦使用Block語法就相當于生成了可賦值給Block類型變量的“值”。
int (^blk)(int);
與前面的使用函數(shù)指針的源代碼對比可知法牲,聲明Block類型變量僅僅是將聲明函數(shù)指針類型變量的“*”變?yōu)椤癪”史汗。該Blcok類型變量與一般的C語言變量完全相同,可作為以下用途使用 :
- 自定變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
那么拒垃,下面我們就試著使用Block語法將Block賦值為Block類型變量:
int (^blk) (int) = ^ (int count) {
return count + 1;
}
也可以:
int (^blk1)(int) = blk;
但是此記述方式極為復(fù)雜停撞。這時,我們可以像使用函數(shù)指針類型時那樣,使用typedef來解決問題戈毒。
typedef int (^blk_t) (int);
如上所示艰猬,通過使用typedef可聲明“blk_t”類型變量。這樣函數(shù)定義就變得更容易理解了埋市。
另外冠桃,將賦值給Block類型變量中的Block方法像C語言通常的函數(shù)調(diào)用那樣使用,這種方法與使用函數(shù)指針類型變量調(diào)用函數(shù)的方法幾乎完全相同道宅。
2.2.3 截獲自動變量值
通過以上說明食听,我們已經(jīng)理解了“帶有自動變量值的匿名函數(shù)”中的“匿名函數(shù)”。而“帶有自動變量值”究竟是什么呢污茵?“帶有自動變量值”在Block中表現(xiàn)為“截獲自動變量值”樱报。截獲自動變量值的實例如下:
int main() {
int val = 10;
void (^blk)(void) = ^ {
printf(val);
}
val = 2;
blk();
//打印結(jié)果為10;
}
Block中泞当,Block表達式截獲所使用的自動變量的值肃弟,既保持該自動變量的瞬間值。這就是自動變量值的截獲零蓉。
2.2.4 __block說明符
實際上笤受,自動變量值截獲指南保持秩序Block語法瞬間的值。保存后就不能改寫該值敌蜂。如果嘗試在Block中改寫截獲的自動變量值箩兽,會發(fā)生編譯錯誤。
若想在Block語法的表達式中將值賦值在Block語法外聲明的自動變量章喉,需要在該自動變量上添加__block說明符汗贫。該變量稱為__block變量。
int main() {
__block int val = 10;
void (^blk)(void) = ^ {
val = 1;
}
val = 2;
blk();
//打印結(jié)果為10秸脱;
}
2.2.5 截獲的自動變量
截獲OC對象落包,調(diào)用變更該對象的方法不會產(chǎn)生編譯錯誤,而向截獲的變量array賦值則會產(chǎn)生編譯錯誤摊唇。
//編譯正常
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^ {
id obj = [[NSObject alloc] init];
[array addObject:obj];
}
//編譯錯誤
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^ {
array = [[NSMutableArray alloc] init];
}
以上的第二段代碼需要給截獲的自動變量附加__block說明符咐蝇。
2.3 Blocks的實現(xiàn)
2.3.1 Block的實質(zhì)
Block上“帶有自動變值的匿名函數(shù)”,但Block究竟是什么呢?
它實際上是作為極普通的C語言源代碼來處理的巷查,通過支持Block的編譯器有序,含有Block的編譯器,含有Block語法的源代碼轉(zhuǎn)換為一般C語言編譯器能夠處理的源代碼岛请,并作為極普通的C語言源代碼被編譯旭寿。
clang(LLVM編譯器)具有轉(zhuǎn)換為我們可讀源代碼的功能。通過“-rewrite-objc”選項就能將含有Block語法的源代碼變換為C++的源代碼崇败。
clang -rewrite-objc xxx.m
其實盅称,所謂Block就是Objective-C對象。Block指針賦值給Block的結(jié)構(gòu)體成員變量isa。
struct _main_block_impl_0 {
void *isa;
int flags;
int Reserved;
void *FuncPtr;
};
此_main_block_impl_0結(jié)構(gòu)體相當于基于objc_object結(jié)構(gòu)體的Objective-C類對象的結(jié)構(gòu)體缩膝。另外搭幻,對其中的成員變量isa進行初始化,具體如下:
isa = &_NSConcreteStackBlock;
既_NSConcreteStackBlock相當于class_t結(jié)構(gòu)體實例逞盆。在將Block作為Objective-C的對象處理時檀蹋,關(guān)于該類的信息放置于_NSConcreteStackBlock中;
2.3.2 截獲自動變量值
本節(jié)主要講解如何截獲自動變量值云芦。將截獲自動值的源代碼用過clang進行轉(zhuǎn)換(源代碼略)俯逾。
我們注意到,Block語法表達式中使用的自動變量被作為成員變量追加到了_main_block_impl_0結(jié)構(gòu)體中舅逸。
struct _main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
int var;
};
_main_block_impl_0結(jié)構(gòu)體內(nèi)聲明的成員變量類型與自動變量類型完全相同桌肴。請注意,Block語法表達式中沒有使用的自動變量不會被追加琉历。Block的自動變量截獲只針對Block中使用的自動變量坠七。
總的來說,所謂“截獲自動變量值”意味著在執(zhí)行Block語法時旗笔,Block語法表達式所使用的自動變量值被保存到Block的結(jié)構(gòu)體實例(既Block自身)中彪置。
2.3.3 __block說明符
以上的截獲自動變量的代碼例子,在Block的結(jié)構(gòu)體實例中重寫該自動變量也不會改變原先截獲的自動變量蝇恶。因為在實現(xiàn)上不能改寫被截獲自動變量的值拳魁,所以會發(fā)生編譯錯誤。
不過這樣以來撮弧,無法在Block中保存值了潘懊,極為不便。但是有兩個方法:
- 有如下幾個變量贿衍,允許Block改寫值:
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
- 使用__block修飾變量 :__block 存儲域類說明符
C語言有如下存儲域類說明符:
- typedef
- extern
- static:表示作為靜態(tài)變量存在在數(shù)據(jù)區(qū)中
- auto:表示作為自動變量存儲在棧中
- register
__block說明符類似于static授舟、auto和register說明符,它們用于指定將變量值設(shè)置到哪個存儲域中贸辈。
個人筆記
2.3.4 Block存儲域
通過前面說明可知释树,Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動變量,__block變量轉(zhuǎn)換為__block變量的結(jié)構(gòu)體類型的自動變量裙椭。所謂結(jié)構(gòu)體類型的自動變量躏哩,既棧上生成的該結(jié)構(gòu)體的實例。
另外通過之前的說明可知Block也是Objective-C對象揉燃,該Block的類為_NSConcreteStackBlock。有很多與之類似的類筋栋,如:
- _NSConcreteStackBlock炊汤,既該類的對象Block設(shè)置在棧上
- _NSConcreteGlobalBlock,設(shè)置在程序的數(shù)據(jù)區(qū)域(.data)中
- _NSConcreteMallocBlock,設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(既堆)中
在記述全局變量的地方使用Block語法時抢腐,生成的Block為_NSConcreteGlobalBlock類對象姑曙。例如
void (^blk)(void) = ^ {
printf("Global Block");
}
此源代碼通過聲明全局變量blk來使用Block語法。如果轉(zhuǎn)換該源代碼迈倍,Block用結(jié)構(gòu)體的成員變量isa的初始化如下:
impl.isa = & _NSConcreteGlobalBlock;
該Block的類為_NSConcreteGlobalBlock類伤靠。此Block既該Block用結(jié)構(gòu)體實例設(shè)置在程序的數(shù)據(jù)區(qū)域中。
在以下情況下啼染,Block為_NSConcreteGlobalBlock類對象
- 記述全局變量的地方有Block語法時
- Block語法的表達式中不使用應(yīng)截獲的自動變量時
除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象宴合,且設(shè)置在棧上。
配置在全局變量上的Block迹鹅,從變量作用域外也可以通過指針安全的使用卦洽。但設(shè)置在棧上的Block,如果其所屬的變量作用域結(jié)束斜棚,該Block就被廢棄阀蒂。由于__block變量也配置在棧上,同樣的弟蚀,如果其所屬的變量作用域結(jié)束蚤霞,則該__block變量也會被廢棄。
Block提供了將Block和__block變量從棧上復(fù)制到堆上的方法來解決這個問題义钉。將配置在棧上的Block復(fù)制到堆上争便,這樣即使Block語法記述的變量作用域結(jié)束,堆上的Block還可以繼續(xù)存在断医。
復(fù)制到堆上的Block將_NSConcreteMallocBlock類對象寫入Block用結(jié)構(gòu)體實例的成員變量isa滞乙。
impl.isa = & _NSConcreteMallocBlock;
而__block變量用結(jié)構(gòu)體成員變量_forwarding可以實現(xiàn)無論__block變量配置在棧上還是堆上時都能夠正確的訪問__block變量。在此情形下鉴嗤,只要棧上的結(jié)構(gòu)體實例成員變量__forwarding指向堆上的結(jié)構(gòu)體實例斩启,那么不管是從棧上的__block變量還是從堆上的__block變量都能夠正確的訪問。
那么Block提供的復(fù)制方法是什么呢醉锅?當ARC時兔簇,大多數(shù)情形下編譯器會恰當?shù)呐袛啵詣由蓪lock從棧上復(fù)制到堆上的代碼硬耍。
當Block作為函數(shù)返回值返回時垄琐,執(zhí)行objc_retainBlock方法,實際上是copy函數(shù)经柴。
那么少數(shù)情形有幾種呢狸窘?
- XXXX
另外,對于已配置在堆上的Block以及配置在程序的數(shù)據(jù)區(qū)域的Block坯认,調(diào)用copy方法又會如何呢翻擒?
- _NSConcreteMallocBlock:引用計數(shù)增加
- _NSConcreteStackBlock:從棧復(fù)制到堆
- _NSConcreteGlobalBlock:什么也不做
不管是Block配置在何處氓涣,用copy方法復(fù)制都不會引起任何問題。在不確定時調(diào)用copy方法即可
2.3.5 __block變量存儲域
上節(jié)只對Block的copy進行了說明陋气,使用__block變量的Block從棧復(fù)制到堆時劳吠,使用的所有__block變量也必定配置在棧上。這些__block變量也全部從棧復(fù)制到堆巩趁。此時痒玩,Block持有__block變量。
如果配置在堆上的Block被廢棄议慰,那么它所使用的__block變量也就被釋放蠢古。
此思考方式與OC的引用計數(shù)內(nèi)存管理完全相同。使用__block變量的Block持有__block變量褒脯。日光Block被廢棄便瑟,它所持有的__block變量也就被釋放。
那么在理解了__block變量的存儲域之后番川,在回顧下之前講過的使用__block變量用結(jié)構(gòu)體成員變量__forwarding的原因到涂。“不管__block變量配置在棧上還是在堆上颁督,都能夠正確的訪問該變量”践啄。正如這句話所述,通過Block的復(fù)制沉御,__block變量也從棧復(fù)制到堆屿讽。此時可同時訪問棧上的__block變量和堆上的__block變量。
源代碼可轉(zhuǎn)換為如下形式:
(val.__forwarding->val);
在變換Block語法的函數(shù)中吠裆,該變量val為復(fù)制到堆上的__block變量用結(jié)構(gòu)體實例伐谈,而使用的與Block無關(guān)的變量val,為復(fù)制前棧上的__block變量用結(jié)構(gòu)體實例试疙。
但是棧上的__block變量用結(jié)構(gòu)體實例在__block變量從棧復(fù)制帶堆上時诵棵,會將成員變量__forwarding的值替換為復(fù)制目標堆上的__block變量用結(jié)構(gòu)體實例的地址。
通過該功能祝旷,無論上在Block語法中履澳、block語法外使用__block變量,還是__block變量配置在棧上或堆上怀跛,都可以順利的訪問同一個__block變量距贷。
2.3.6 截獲對象
以下源代碼生成并持有NSMutableArray類的對象,由于附有__strong修飾符的賦值目標變量的作用域立即結(jié)束吻谋,因此對象被立即釋放并廢棄忠蝗。
{
id array = [[NSMutableArray alloc] init];
}
下面我們看一下在Block語法中使用該變量array的代碼:
//運行正常
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
該代碼運行正常,執(zhí)行結(jié)果如下
array count = 1;
array count = 2;
array count = 3;
請注意被賦值NSMutableArray類對象并被截獲的自動變量array滨溉。我們可以發(fā)現(xiàn)它是Block用的結(jié)構(gòu)體中附有__strong修飾符的成員變量什湘。
struct _main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
id __strong array;
};
在OC中长赞,C語言結(jié)構(gòu)體不能含有附有__strong修飾的變量晦攒。因為編譯器不知道應(yīng)何時進行C語言結(jié)構(gòu)體的初始化和廢棄操作闽撤,不能很好的管理內(nèi)存。
但是OC的運行時庫能夠很準確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時機脯颜,因此Block用結(jié)構(gòu)體中即使含有附有__strong修飾符或__weak修飾符的變量哟旗,也可以恰當?shù)倪M行初始化和廢棄。為此需要使用在__main_block_desc_0結(jié)構(gòu)體中增加的成員變量copy和dispose栋操,以及作為指針賦值給該成員變量的_main_block_copy_0函數(shù)和_main_block_dispose_0函數(shù)闸餐。
恰當管理賦值給變量array的對象:__main_block_copy_0函數(shù)使用_Block_object_assign函數(shù)將對象類型對象復(fù)制給Block用結(jié)構(gòu)體的成員變量array中并持有該對象。
_Block_object_assign函數(shù)調(diào)用相當于retain實例方法的函數(shù)矾芙,將對象賦值在對象類型的結(jié)構(gòu)體成員變量中舍沙。
另外,__main_block_dispose_0函數(shù)使用_Block_object_dispose函數(shù)剔宪,釋放賦值在Block用結(jié)構(gòu)體成員變量array中的對象拂铡。
_Block_object_dispose函數(shù)調(diào)用相當于release實例方法的函數(shù),釋放賦值在對象類型的結(jié)構(gòu)體成員變量中的對象葱绒。
雖然此__main_block_copy_0函數(shù)(以下簡稱copy函數(shù))和__main_block_dispose_0函數(shù)(以下簡稱dispose函數(shù))指針被賦值在__main_block_desc_0結(jié)構(gòu)體成員變量copy和dispose感帅。在Block從棧復(fù)制到堆時以及堆上的Block被廢棄時會調(diào)用這些函數(shù)。
- copy函數(shù):棧上的Block復(fù)制到堆時地淀;
- dispose函數(shù):堆上的Block被廢棄時失球;
那么什么時候棧上的Block會復(fù)制到堆呢?
- 調(diào)用Block的copy實例方法時帮毁;
- Block作為函數(shù)返回值返回時实苞;
- 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時;
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時烈疚;
在上面這些情況下棧上的Block賦值到堆上黔牵,其實可歸結(jié)為_Block_copy函數(shù)被調(diào)用時Block從棧復(fù)制到堆。相對的胞得,在釋放復(fù)制到堆上的Block后荧止,誰都不持有Block而使其被廢棄時調(diào)用dispose函數(shù)。這相當于對象的dealloc實例方法阶剑。
有了這種構(gòu)造跃巡,通過使用附有__strong修飾符的自動變量,因而Block中截獲的對象就能夠超出其變量作用域而存在牧愁。
2.3.7 __block變量和對象
__block說明符可指定任何類型的自動變量素邪。
__block id obj = [[NSObject alloc] init];
其實該代碼等同于
__block id __strong obj = [[NSObject alloc] init];
ARC有效時彼水,id類型以及對象類型變量必定附加所有權(quán)修飾符氓皱,缺省為附有__strong修飾符的變量住拭。
在Block中使用附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那闆r下摇零,當Block從棧復(fù)制到堆時,使用Block_object_assign函數(shù)沽甥,持有Block截獲的對象声邦。當堆上的Block被廢棄時,使用_block_object_dispose函數(shù)摆舟,釋放Block截獲的對象亥曹。
在__block變量為附有_strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那樾蜗聲l(fā)生同樣的過程。當__block變量從棧復(fù)制到堆時恨诱,使用_Block_object_assign函數(shù)媳瞪,持有賦值給__block變量的對象。當堆上的__block變量被廢棄時照宝,使用_Block_object_dispose函數(shù)蛇受,釋放賦值給__block變量的對象。
由此可知厕鹃,即使對象賦值復(fù)制到堆上的附有_strong修飾符的對象類型__block變量中兢仰,只要__block變量在堆上繼續(xù)存在,那么該對象就會繼續(xù)處于被持有的狀態(tài)熊响。這與Block中使用賦值給附有__strong修飾符的對象類型自動變量的對象相同旨别。
另外,我們前面用到的只有附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞亢骨选H绻褂胈_weak修飾符會如何呢秸弛?首先是在Block中使用附有__weak修飾符的id類型變量的情況。
blk_t blk;
{
id array = [[[NSMutableArray alloc] init];
id __weak array2 = array;
blk = [^(id obj) {
[array2 addObject:obj];
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
該代碼可正常執(zhí)行洪碳。 執(zhí)行結(jié)果递览,這與以上代碼的結(jié)果不同:
array2 count = 0;
array2 count = 0;
array2 count = 0;
這是由于附有__strong修飾符的變量array在該變量作用域結(jié)束的同時被釋放、廢棄瞳腌,nil被賦值在附有__weak修飾符的變量array2中绞铃。
2.3.8 Block循環(huán)引用
如果在Block中使用附有__strong修飾符的對象類型自動變量,那么當Block從棧復(fù)制到堆時嫂侍,該對象為Block所持有儿捧。這樣容易引起循環(huán)利用。我們來看看下面的源代碼:
typedef void (^blk_t)(void);
@interface MOyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
blk_ = ^ {
NSLog(@"self = %@",self);
} ;
return self;
}
- (void)dealloc {
NSLog(@:dealloc:);
}
@end
int main() {
id o = [[MyObject alloc] init];
NSLog(@"%@",o);
return 0;
}
該源代碼中MyObject類的dealloc實例方法一定沒有被調(diào)用挑宠。
MyObject類對象的Block類型成員變量blk_持有賦值為Block的強引用菲盾。既MyObject類對象持有Nlock。init實例方法中執(zhí)行的Block語法使用附有_strong修飾符的id類型變量self各淀。并且由于Block語法賦值在了成員變量blk中懒鉴,因此通過Block語法生成在棧上的Block此時由棧復(fù)制到堆,并持有所使用的self。self持有Block临谱,Block持有self璃俗。這正是循環(huán)引用。
另外悉默,編譯器在編譯該源代碼是能夠查處循環(huán)引用城豁,因此編譯器能正確的進行警告。
為避免此循環(huán)引用麦牺,可聲明附有__weak修飾符的變量钮蛛,并將self賦值使用鞭缭。
- (id)init {
self = [super init];
id __weak tmp = self;
blk_ = ^ {
NSLog(@"self = %@",tmp);
} ;
return self;
}
在該源代碼中剖膳,由于Block存在時,持有該Block的MyObject類對象賦值在變量tmp中的self必須存在岭辣,因此不需要判斷tmp的值是否為nil吱晒。
在面相iOS4(MRC),必須使用__unsafe_unretained修飾符代替__weak修飾符沦童。在此源代碼中也可使用__unsafe_unretained修飾符仑濒,且不必擔心懸掛指針。
- (id)init {
self = [super init];
id __unsafe_unretained tmp = self;
blk_ = ^ {
NSLog(@"self = %@",tmp);
} ;
return self;
}
另外在以下源代碼中Block內(nèi)沒有使用self也同樣截獲了self偷遗,引起了循環(huán)引用墩瞳。
typedef void (^blk_t)(void);
@interface MOyObject : NSObject
{
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
blk_ = ^ {
NSLog(@"obj_ = %@",obj_);
} ;
return self;
}
既Block語法內(nèi)使用的obj_實際上截獲了self。對編譯器來說氏豌,obj_只不過是對象用結(jié)構(gòu)體的成員變量喉酌。
blk_ = ^ {
NSLog(@"obj_ = %@",self->obj_);
};
該源代碼也基本與前面一樣,聲明附有_weak修飾符的變量并賦值obj使用來避免循環(huán)引用泵喘。在此源代碼中也可安全的使用__unsafe_unretained修飾符泪电,原因同上。
- (id)init {
self = [super init];
id __weak obj = obj_;
blk_ = ^ {
NSLog(@"obj = %@",obj);
} ;
return self;
}
在為避免循環(huán)引用而使用__weak修飾符時纪铺,雖說可以確認使用附有__weak修飾符的變量時是否為nil相速,但更有必要使之生成以使用賦值給附有__weak修飾符變量的對象。
另外鲜锚,還可以使用__block變量來避免循環(huán)引用突诬。
typedef void (^blk_t)(void);
@interface MOyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
__block id tmp = self;
blk_ = ^ {
NSLog(@"self = %@",tmp);
tmp = nil;
} ;
return self;
}
- (void)execBlock {
blk();
}
- (void)dealloc {
NSLog(@:dealloc:);
}
@end
int main() {
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
該源代碼沒有循環(huán)引用。原因:通過執(zhí)行execBlock實例方法芜繁,Block被實行旺隙,nil被賦值在__block變量tmp中。因此浆洗,_block變量tmp對MyObject類對象的強引用失效催束。但是如果不調(diào)用execBlock實例方法,既不執(zhí)行賦值給成員變量blk的Block伏社,便會循環(huán)引用并引起內(nèi)存泄漏抠刺。
在生成并持有MyObject類對象的狀態(tài)下會引起以下循環(huán)引用:
- MyObject類對象持有Block;
- Block持有__block變量;
- __block變量持有MyObject類對象;
下面我們對使用__block變量避免循環(huán)引用的方法和使用__weak 修飾符及__unsafe_unretained修飾符避免循環(huán)引用的方法做個比較塔淤。
使用__block變量的優(yōu)點如下:
- 通過__block變量可控制對象的持有期間
- 在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可(不必擔心懸垂指針)
在執(zhí)行Block時可動態(tài)的決定是否將nil或其他對象賦值在__block變量中。
使用__block變量的缺點如下:
- 為避免循環(huán)引用必須執(zhí)行Block
存在執(zhí)行了Block語法速妖,卻不執(zhí)行Block的路徑時高蜂,無法避免循環(huán)引用。若有雨Block引發(fā)了循環(huán)引用時罕容,根據(jù)Block的用途選擇使用__block變量备恤、__weak修飾符或__unsafe_unretained修飾符來避免循環(huán)引用。
2.3.9 copy/release
ARC無效時锦秒,一般需要手動將Block從棧復(fù)制到堆露泊。另外,由于ARC無效旅择,所以肯定要釋放賦值的Block惭笑。這時我們用copy實例方法用來賦值,用release實例方法來釋放生真。
[blk_ release];
只要Block有一次復(fù)制并配置在堆上沉噩,就可通過retain實例方法持有。
[blk_ retain];
但是對于配置在棧上的Block調(diào)用retain實例方法則不起任何作用柱蟀。
[blk_ retain];
該源代碼中川蒙,雖然堆賦值給blk_的棧上的Block調(diào)用了retain實例方法,但實際上對此源代碼不起任何作用长已。因此推薦使用copy實例方法來持有Block畜眨。
另外,ARC無效時痰哨,__block說明符被用來避免Block中的循環(huán)引用胶果。這是由于當Block從棧復(fù)制到堆時,若Block使用的變量為附有__block說明符的id類型或?qū)ο箢愋偷淖詣幼兞拷锔粫籸etain早抠;若Block使用的變量為沒有__block說明符的id類型或?qū)ο箢愋偷淖詣幼兞浚瑒t被retain撬讽。
注意:正好在ARC有效時能夠同__unsafe_unretained修飾符一樣來使用蕊连。由于ARC有效時和無效時__block說明符的用途有很大的區(qū)別,因此在編寫源代碼時游昼,必須知道該源代碼是在ARC有效情況下編譯還是在ARC無效情況下編譯甘苍。