iOS開發(fā)讀書筆記:Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理-中篇(Blocks)

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中保存值了潘懊,極為不便。但是有兩個方法:

  1. 有如下幾個變量贿衍,允許Block改寫值:
    • 靜態(tài)變量
    • 靜態(tài)全局變量
    • 全局變量
  2. 使用__block修飾變量 :__block 存儲域類說明符

C語言有如下存儲域類說明符:

  1. typedef
  2. extern
  3. static:表示作為靜態(tài)變量存在在數(shù)據(jù)區(qū)中
  4. auto:表示作為自動變量存儲在棧中
  5. 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ù)情形有幾種呢狸窘?

  1. 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無效情況下編譯甘苍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市烘豌,隨后出現(xiàn)的幾起案子载庭,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囚聚,死亡現(xiàn)場離奇詭異靖榕,居然都是意外死亡,警方通過查閱死者的電腦和手機顽铸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門茁计,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谓松,你說我怎么就攤上這事星压。” “怎么了鬼譬?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵娜膘,是天一觀的道長。 經(jīng)常有香客問我拧簸,道長劲绪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任盆赤,我火速辦了婚禮,結(jié)果婚禮上歉眷,老公的妹妹穿的比我還像新娘牺六。我一直安慰自己,他們只是感情好汗捡,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布淑际。 她就那樣靜靜地躺著,像睡著了一般扇住。 火紅的嫁衣襯著肌膚如雪春缕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天艘蹋,我揣著相機與錄音锄贼,去河邊找鬼。 笑死女阀,一個胖子當著我的面吹牛宅荤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浸策,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼冯键,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了庸汗?” 一聲冷哼從身側(cè)響起惫确,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后改化,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昧诱,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年所袁,在試婚紗的時候發(fā)現(xiàn)自己被綠了盏档。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡燥爷,死狀恐怖蜈亩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情前翎,我是刑警寧澤稚配,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站港华,受9級特大地震影響道川,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜立宜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一冒萄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧橙数,春花似錦尊流、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钟哥,卻和暖如春迎献,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腻贰。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工吁恍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人银受。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓践盼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宾巍。 傳聞我的和親對象是個殘疾皇子咕幻,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344