前言
需要先知道的
Objective-C 轉(zhuǎn) C++的方法
因?yàn)樾枰碆lock操作的C++源碼,所以需要知道轉(zhuǎn)換的方法,自己轉(zhuǎn)過(guò)來(lái)看一看:
1. 在OC源文件block.m寫(xiě)好代碼。
2. 打開(kāi)終端,cd到block.m所在文件夾。
3. 輸入clang -rewrite-objc block.m,就會(huì)在當(dāng)前文件夾內(nèi)自動(dòng)生成對(duì)應(yīng)的block.cpp文件。
關(guān)于幾種變量的特點(diǎn)
c語(yǔ)言的函數(shù)中可能使用的變量:
- 函數(shù)的參數(shù)
- 自動(dòng)變量(局部變量)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
而且,由于存儲(chǔ)區(qū)域特殊,這其中有三種變量是可以在任何時(shí)候以任何狀態(tài)調(diào)用的:
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
而其他兩種,則是有各自相應(yīng)的作用域,超過(guò)作用域后,會(huì)被銷(xiāo)毀。
2.1 Blocks概要
2.2.1 什么是Blocks
- Blocks是C語(yǔ)言的擴(kuò)充功能——“帶有自動(dòng)變量(即局部變量)的匿名函數(shù)”。
- Blocks提供了類(lèi)似由C++和oc類(lèi)生成實(shí)例或?qū)ο髞?lái)保持變量值得方法,其代碼量與編寫(xiě)C語(yǔ)言函數(shù)差不多;
- 使用Blocks可以不聲明C++和oc類(lèi),也沒(méi)有使用靜態(tài)變量、靜態(tài)全局變量或全局變量時(shí)的問(wèn)題,僅用編寫(xiě)C語(yǔ)言函數(shù)的源代碼量即可使用帶有自動(dòng)變量值的匿名函數(shù)。
2.2 Blocks模式
2.2.1 Block 語(yǔ)法
-
與一般的C語(yǔ)言函數(shù)定義相比,完整形式的Block語(yǔ)法有兩點(diǎn)不同:
- 沒(méi)有函數(shù)名稱(chēng)(因?yàn)槭悄涿瘮?shù))
- 帶有“^”(便于查找)
-
Block表達(dá)式完整語(yǔ)法
圖2-1 Block 語(yǔ)法.png -
省略返回值類(lèi)型的Block語(yǔ)法
圖2-2 Block語(yǔ)法省略返回值類(lèi)型.png
*省略返回值類(lèi)型時(shí),如果表達(dá)式中有return語(yǔ)句,就使用該返回值的類(lèi)型,如果表達(dá)式中沒(méi)有return語(yǔ)句,就使用void類(lèi)型。表達(dá)式中含有多個(gè)return語(yǔ)句時(shí),所以return語(yǔ)句的返回值類(lèi)型必須相同
-
省略返回值類(lèi)型和參數(shù)列表的Block語(yǔ)法
圖2-3 Block語(yǔ)法省略返回值類(lèi)型和參數(shù)列表.png
2.2.2 Block類(lèi)型變量(即Block變量)
在Block語(yǔ)法下,可將Block語(yǔ)法賦值給聲明為Block類(lèi)型的變量中(即源代碼中一旦使用Block語(yǔ)法就相當(dāng)于生成了可賦值給Block類(lèi)型變量的“值”)?!癇lock”即指源代碼中的Block語(yǔ)法,也指由Block語(yǔ)法所生成的值。
- 使用Block語(yǔ)法將Block賦值為Block類(lèi)型變量。
int(^blk)(int) = ^(int count){return count + 1;};
//等號(hào)左側(cè)的代碼表示了這個(gè)Block的類(lèi)型:它接受一個(gè)int參數(shù),返回一個(gè)int值。
//等號(hào)右側(cè)的代碼是這個(gè)Block的值:它是等號(hào)左側(cè)定義的block類(lèi)型的一種實(shí)現(xiàn)。
- 由Block類(lèi)型變量向Block類(lèi)型變量賦值。
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;
- 在函數(shù)參數(shù)中使用Block類(lèi)型變量可以向函數(shù)傳遞Block。
void func(int (^blk)(int))
{
}
- 在函數(shù)返回值中指定Block類(lèi)型,可以將Block作為函數(shù)的返回值返回。
int (^func()(int))
{
return ^(int count) {return count + 1;};
}
- 在函數(shù)參數(shù)和返回值中使用Block類(lèi)型變量時(shí),可以通過(guò)typedef為Block類(lèi)型提供別名,從而起到簡(jiǎn)化塊類(lèi)型變量名的作用。
tupedef int (^blk_t) (int);
- 通過(guò)Block類(lèi)型變量調(diào)用Block與C語(yǔ)言通常的函數(shù)調(diào)用沒(méi)有區(qū)別(例如,在函數(shù)和方法中可以將Block類(lèi)型變量作為參數(shù))。
- (int) methodUsingBlock:(blk_t )blk rate:(int )rate
{
return blk (rate);
}
- Block類(lèi)型變量可完全像通常的C語(yǔ)言變量一樣使用(例如,可以使用指向Block類(lèi)型變量的指針,即Block的指針類(lèi)型變量)。
typedef int (^blk_t) (int);
blk_t blk = ^(int count ) {return count + 1;};
blk_t * blkptr = &blk;
(*blkptr)(10);
2.2.3 截獲自動(dòng)變量值
Blocks中,Block常量表達(dá)式會(huì)截獲所使用的自動(dòng)變量的值(即保存該自動(dòng)變量的瞬間值),從而在執(zhí)行塊時(shí)使用。
int main()
{
int dmy = 256;
int val = 10;
const char * fmt = "val = %d\n";
void (^blk )(void ) = ^{printf(fmt ,val );};
val = 2;
fmt = "These valuse were changed. val = %d\n";
blk();
return 0;
}
輸出結(jié)果:
val = 10;
分析:Blocks中,Block表達(dá)式截獲所使用的自動(dòng)變量的值,即保存該自動(dòng)變量的瞬間值,因?yàn)閎lock表達(dá)式保存了自動(dòng)變量的值,所以在執(zhí)行block語(yǔ)法后,即使改寫(xiě)block中使用的自動(dòng)變量的值也不會(huì)影響B(tài)lock執(zhí)行時(shí)自動(dòng)變量的值。
2.2.4 __block說(shuō)明符(即存儲(chǔ)類(lèi)型修改符)
使用附有__block說(shuō)明符的自動(dòng)變量可在Block中賦值,該變量稱(chēng)為_(kāi)_block變量。
2.2.5 截獲的自動(dòng)變量
- 如果將值賦值給Block中截獲的自動(dòng)變量,就會(huì)產(chǎn)生編譯錯(cuò)誤。這種情況下,需要給截獲的自動(dòng)變量附加__block說(shuō)明符。
- 截獲Objective-C對(duì)象,調(diào)用變更該對(duì)象的方法并不會(huì)產(chǎn)生編譯錯(cuò)誤,但是,向截獲的自動(dòng)變量(即所截獲的Objective-C對(duì)象)賦值則會(huì)產(chǎn)生錯(cuò)誤。總之,賦值給截獲的自動(dòng)變量會(huì)產(chǎn)生編譯錯(cuò)誤,但使用截獲的值卻不會(huì)有任何問(wèn)題。
- 在現(xiàn)在的Block中,截獲自動(dòng)變量的方法并沒(méi)有實(shí)現(xiàn)對(duì)C語(yǔ)言數(shù)組的截獲,但是,使用指針可以解決該問(wèn)題。
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
blk();
2.3 Blocks的實(shí)現(xiàn)
2.3.1 Block的實(shí)質(zhì)
- 總結(jié):表層實(shí)質(zhì)是一種類(lèi)型,深層實(shí)質(zhì)是一種oc對(duì)象;
- 通過(guò)支持Block的編譯器,含有Block語(yǔ)法的源代碼轉(zhuǎn)換為一般C語(yǔ)言編譯器能夠處理的源代碼,并作為極為普通的C語(yǔ)言源代碼被編譯。
- 這不過(guò)是概念上的問(wèn)題,在實(shí)際編譯時(shí)無(wú)法轉(zhuǎn)換成我們能夠理解的源代碼,Clang(LLVM編譯器)具有將含有Block語(yǔ)法的源代碼轉(zhuǎn)換為我們可讀源代碼的功能。通過(guò)“-rewrite-objc”選項(xiàng)就能將含有Block語(yǔ)法的源代碼變換為C++的源代碼(本質(zhì)是使用了struct結(jié)構(gòu)的C語(yǔ)言源代碼)。
int main()
{
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
通過(guò)clang轉(zhuǎn)換為以下形式:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
void *isa;
int Flags; // 標(biāo)志
int Reserved; // 今后版本升級(jí)所需的區(qū)域
void *FuncPtr; // 函數(shù)指針
};
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 該結(jié)構(gòu)體的構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
// _NSConcreteStackBlock用于初始化__block_impl結(jié)構(gòu)體的isa成員
// (將Block指針賦值給Block的結(jié)構(gòu)體成員變量isa)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 最初的源代碼中的Block語(yǔ)法經(jīng)clang變換,被處理成簡(jiǎn)單的C語(yǔ)言函數(shù)(該函數(shù)以Block語(yǔ)法所屬的函數(shù)名——main和該Block語(yǔ)法在該函數(shù)出現(xiàn)的順序值——0來(lái)命名)。
// __ceself為指向Block值的變量。
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved; // 今后版本升級(jí)所需的區(qū)域
unsigned long Block_size; // Block的大小
} __mian_block_desc_0_DATA = { // 該結(jié)構(gòu)體實(shí)例的初始化部分
0,
sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結(jié)構(gòu)體實(shí)例)的大小進(jìn)行初始化
};
// main函數(shù),從這里開(kāi)始閱讀源代碼
int main()
{
// 調(diào)用結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)__main_block_impl_0
void (*blk)(void) =
(void (*)(void)) & __main_block_impl_0(
(void *)__main_block_func_0, &__mian_block_desc_0_DATA);
/*
去掉轉(zhuǎn)換部分,如下:
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
這段代碼對(duì)應(yīng):
void (^blk)(void) = ^{printf("Block\n");};
理解:
1. 將__main_block_impl_0結(jié)構(gòu)體類(lèi)型的自動(dòng)變量(即棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例的指針)賦值給__main_block_impl_0結(jié)構(gòu)體指針類(lèi)型的變量blk
2. __main_block_func_0是由Block語(yǔ)法轉(zhuǎn)換的C語(yǔ)言函數(shù)指針。
3. __main_block_desc_0_DATA作為靜態(tài)全局變量初始化的__main_block_desc_0結(jié)構(gòu)體實(shí)例指針
4. 將__main_block_impl_0的__block_impl進(jìn)行展開(kāi),__main_block_impl_0結(jié)構(gòu)體根據(jù)構(gòu)造函數(shù)會(huì)像下面進(jìn)行初始化:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
*/
((void (*)(struct __block_impl *))(
(struct __block_impl *)blk)->FuncPtr) ((struct __block_impl *)blk);
/*
去掉轉(zhuǎn)換部分,如下:
(*blk->impl.FuncPtr)(blk);
這段代碼對(duì)應(yīng):
blk();
理解:
1. 使用函數(shù)指針調(diào)用函數(shù)
2. 由Block語(yǔ)法轉(zhuǎn)換的__main_block_func_0函數(shù)的指針被賦值成員變量FuncPtr中
3. __main_block_func_0函數(shù)的參數(shù)__cself指向Block值,在調(diào)用該函數(shù)的源代碼中可以看出Block正是作為參數(shù)進(jìn)行了傳遞
*/
return 0;
}
OC類(lèi)和對(duì)象的實(shí)質(zhì)(Block就是oc對(duì)象)
- 在弄清楚Block就是Objective-C對(duì)象前,要先理解objc_object結(jié)構(gòu)體和objc_class結(jié)構(gòu)體。
- id類(lèi)型是objc_object結(jié)構(gòu)體的指針類(lèi)型。
typedef struct objc_object {
Class isa;
} *id;
- Class是objc_class結(jié)構(gòu)體的指針類(lèi)型。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
} ;
- objc_object結(jié)構(gòu)體和objc_class結(jié)構(gòu)體歸根到底是各個(gè)對(duì)象和類(lèi)的實(shí)現(xiàn)中最基本的結(jié)構(gòu)體。
- 如下,通過(guò)一個(gè)簡(jiǎn)單的MyObject類(lèi)來(lái)說(shuō)明Objective-C類(lèi)與對(duì)象的實(shí)質(zhì):
@interface MyObject : NSObject
{
int val0;
int val1;
}
- 基于objc_object結(jié)構(gòu)體,該類(lèi)的對(duì)象的結(jié)構(gòu)體如下:
struct MyObject {
Class isa; // 成員變量isa持有該類(lèi)的結(jié)構(gòu)體實(shí)例指針
int val0; // 原先MyObject類(lèi)的實(shí)例變量val0和val1被直接聲明為成員變量
int val1;
}
理解:
- MyObject類(lèi)的實(shí)例變量val0和val1被直接聲明為對(duì)象的成員變量。
- “Objective-C中由類(lèi)生成對(duì)象”意味著,像該結(jié)構(gòu)體這樣“生成由該類(lèi)生成的對(duì)象的結(jié)構(gòu)體實(shí)例”。
- 生成的各個(gè)對(duì)象(即由該類(lèi)生成的對(duì)象的各個(gè)結(jié)構(gòu)體實(shí)例),通過(guò)成員變量isa保持該類(lèi)的結(jié)構(gòu)體實(shí)例指針。

各類(lèi)的結(jié)構(gòu)體是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體:
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
}
- 理解:
- 在Objective-C中,比如NSObject的class_t結(jié)構(gòu)體實(shí)例以及NSMutableArray的class_t結(jié)構(gòu)體實(shí)例等,均生成并保持各個(gè)類(lèi)的class_t結(jié)構(gòu)體實(shí)例。
- 該實(shí)例持有聲明的成員變量、方法的名稱(chēng)、方法的實(shí)現(xiàn)(即函數(shù)指針)、屬性以及父類(lèi)的指針,并被Objective-C運(yùn)行時(shí)庫(kù)所使用。
- 回到正題——“Block就是Objective-C對(duì)象”,*** 先看Block結(jié)構(gòu)體:***
struct __main_block_impl_0 {
void *isa;
int Flags; // 標(biāo)志
int Reserved; // 今后版本升級(jí)所需的區(qū)域
void *FuncPtr; // 函數(shù)指針
struct __main_block_desc_0* Desc;
};
理解:
- 此__main_block_impl_0結(jié)構(gòu)體相當(dāng)于基于objc_object結(jié)構(gòu)體的Objective-C類(lèi)的對(duì)象的結(jié)構(gòu)體。
- 對(duì)其中的isa進(jìn)行初始化,如
isa = &_NSConcreteStackBlock;
即_NSConcreteStackBlock相當(dāng)于class_t結(jié)構(gòu)體實(shí)例
- 在將Block作為Objective-C的對(duì)象處理時(shí),關(guān)于該類(lèi)的信息放置于_NSConcreteStackBlock中。
2.3.2 獲取自動(dòng)變量值
截獲自動(dòng)變量值的源代碼經(jīng)clang轉(zhuǎn)換,如下:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt; // Block語(yǔ)法表達(dá)式“使用的自動(dòng)變量”被追加到該結(jié)構(gòu)體
int val;
// 構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 靜態(tài)函數(shù) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
const char *fmt = __cself->fmt;
int val = __cself->val;
/*
理解:
1. __main_block_impl_0結(jié)構(gòu)體實(shí)例(即Block)所截獲的自動(dòng)變量在Block語(yǔ)法表達(dá)式執(zhí)行之前就被聲明定義,所以,在Objective-C的源代碼中,執(zhí)行Block語(yǔ)法表達(dá)式時(shí)無(wú)需改動(dòng)便可使用截獲的自動(dòng)變量值。
2. "截獲自動(dòng)變量值"意味著在執(zhí)行Block語(yǔ)法時(shí),Block語(yǔ)法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例(即Block自身)中。
3. Block不能直接使用“C語(yǔ)言數(shù)組類(lèi)型的自動(dòng)變量”,所以,截獲自動(dòng)變量時(shí),會(huì)將其值傳遞給結(jié)構(gòu)體的構(gòu)造函數(shù)進(jìn)行保存
*/
printf(fmt, val);
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
// 主函數(shù),從這里開(kāi)始閱讀源代碼
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
// 調(diào)用結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)初始化該結(jié)構(gòu)體實(shí)例
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);
/*
理解:
1. 在初始化結(jié)構(gòu)體實(shí)例時(shí),會(huì)根據(jù)傳遞給構(gòu)造函數(shù)的參數(shù)對(duì)由自動(dòng)變量追加的成員變量進(jìn)行初始化(即執(zhí)行Block語(yǔ)法使用的自動(dòng)變量fmt和val會(huì)初始化結(jié)構(gòu)體實(shí)例)
2. __main_block_impl_0結(jié)構(gòu)體實(shí)例的初始化如下:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
fmt = "val = %d\n";
val = 10;
3. 由上可知,在__main_block_impl_0結(jié)構(gòu)體實(shí)例(即Block)中,自動(dòng)變量被截獲。
*/
return 0;
}
2.3.3 __block說(shuō)明符
截獲自動(dòng)變量值的源代碼經(jīng)clang轉(zhuǎn)換,如下:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt; // Block語(yǔ)法表達(dá)式“使用的自動(dòng)變量”被追加到該結(jié)構(gòu)體
int val;
// 構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 靜態(tài)函數(shù) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
const char *fmt = __cself->fmt;
int val = __cself->val;
/*
理解:
1. __main_block_impl_0結(jié)構(gòu)體實(shí)例(即Block)所截獲的自動(dòng)變量在Block語(yǔ)法表達(dá)式執(zhí)行之前就被聲明定義,所以,在Objective-C的源代碼中,執(zhí)行Block語(yǔ)法表達(dá)式時(shí)無(wú)需改動(dòng)便可使用截獲的自動(dòng)變量值。
2. "截獲自動(dòng)變量值"意味著在執(zhí)行Block語(yǔ)法時(shí),Block語(yǔ)法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例(即Block自身)中。
3. Block不能直接使用“C語(yǔ)言數(shù)組類(lèi)型的自動(dòng)變量”,所以,截獲自動(dòng)變量時(shí),會(huì)將其值傳遞給結(jié)構(gòu)體的構(gòu)造函數(shù)進(jìn)行保存
*/
printf(fmt, val);
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
// 主函數(shù),從這里開(kāi)始閱讀源代碼
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
// 調(diào)用結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)初始化該結(jié)構(gòu)體實(shí)例
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);
/*
理解:
1. 在初始化結(jié)構(gòu)體實(shí)例時(shí),會(huì)根據(jù)傳遞給構(gòu)造函數(shù)的參數(shù)對(duì)由自動(dòng)變量追加的成員變量進(jìn)行初始化(即執(zhí)行Block語(yǔ)法使用的自動(dòng)變量fmt和val會(huì)初始化結(jié)構(gòu)體實(shí)例)
2. __main_block_impl_0結(jié)構(gòu)體實(shí)例的初始化如下:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
fmt = "val = %d\n";
val = 10;
3. 由上可知,在__main_block_impl_0結(jié)構(gòu)體實(shí)例(即Block)中,自動(dòng)變量被截獲。
*/
return 0;
}
2.3.4 Block存儲(chǔ)域
從“__block說(shuō)明符”一節(jié)中可知,*** Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類(lèi)型的自動(dòng)變量,___block變量轉(zhuǎn)換為_(kāi)_block的結(jié)構(gòu)體類(lèi)型的自動(dòng)變量。所謂結(jié)構(gòu)體類(lèi)型的自動(dòng)變量,即棧上生成的該結(jié)構(gòu)體的實(shí)例。 ***
表 Block與__block變量的實(shí)質(zhì)
| 名稱(chēng) | 實(shí)質(zhì) |
|---|---|
| Block | 棧上block的結(jié)構(gòu)體實(shí)例 |
| _block變量 | 棧上_block變量的結(jié)構(gòu)體實(shí)例 |
從“Block的實(shí)質(zhì)”一節(jié)中可知,Block是Objective-C對(duì)象,并且該Block的類(lèi)為_(kāi)NSConcreteStackBlock。此外,與之類(lèi)似的還有兩個(gè)類(lèi):
_NSConcreteStackBlock —— *** 該類(lèi)對(duì)象設(shè)置在棧上 ***
_NSConcreteGlobalBlock —— *** 該類(lèi)對(duì)象設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中 ***
_NSConcreteMallocBlock —— *** 該類(lèi)對(duì)象設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(即堆中) ***
在內(nèi)存中的位置如下圖:

注意
- 由于_NSConcreteGlobalBlock類(lèi)生成的Block對(duì)象設(shè)置在程序的數(shù)據(jù)區(qū)域中(該類(lèi)會(huì)運(yùn)用在“全局變量”的聲明中),由此該類(lèi)的結(jié)構(gòu)體實(shí)例的內(nèi)容不依賴(lài)于執(zhí)行時(shí)的狀態(tài),所以整個(gè)程序只需一個(gè)實(shí)例。
- 只在截獲自動(dòng)變量時(shí),Block的結(jié)構(gòu)體實(shí)例截獲的值才會(huì)根據(jù)執(zhí)行時(shí)的狀態(tài)變化,而在不截獲自動(dòng)變量時(shí),Block的結(jié)構(gòu)體實(shí)例每次截獲的值都相同。也就是說(shuō),即時(shí)在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語(yǔ)法時(shí),只要Block不截獲自動(dòng)變量,就可以將Block的結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域。
總結(jié)
-
Block為_(kāi)NSConcreteGlobalBlock類(lèi)對(duì)象(即Block配置在程序的數(shù)據(jù)區(qū)域中)的情況有兩種:
- 記述全局變量的地方有Block語(yǔ)法時(shí)
- Block語(yǔ)法表達(dá)式中不使用“應(yīng)截獲的自動(dòng)變量”時(shí)
除此之外的Block語(yǔ)法生成的Block為_(kāi)NSConcreteStackBlock類(lèi)對(duì)象(即類(lèi)對(duì)象設(shè)置在棧上)。
而,_NSConcreteMallocBlock類(lèi)是Block超出變量作用域可存在的原因。
遺留問(wèn)題
“__block說(shuō)明符”一節(jié)中遺留的問(wèn)題:
- Block超出變量作用域可存在的原因
- __block變量的結(jié)構(gòu)體成員變量__forwarding存在的原因
配置在全局變量的Block,從變量作用域外也可以通過(guò)指針安全的使用,而配置在棧上的Block,如果其所屬的變量作用域結(jié)束,該Block就被廢棄。如圖:

為此,Blocks提供了將Block和__block變量從棧上復(fù)制到堆上的方法來(lái)解決這個(gè)問(wèn)題。
如圖:

實(shí)現(xiàn)機(jī)制:
- 復(fù)制到堆上的Block將_NSConcreteMallocBlock類(lèi)對(duì)象寫(xiě)入Block的結(jié)構(gòu)體實(shí)例的成員變量isa。
impl.isa = &_NSConcreteMallocBlock;
而__block變量的結(jié)構(gòu)體成員變量forwarding可以實(shí)現(xiàn)無(wú)論變量配置在棧上還是堆上時(shí)都能夠正確地訪問(wèn)__block變量。
ARC有效時(shí),大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實(shí)現(xiàn)
實(shí)際上,ARC有效時(shí),大多數(shù)編譯器會(huì)恰當(dāng)?shù)剡M(jìn)行判斷,自動(dòng)生成將Block從棧上復(fù)制到堆上的代碼。
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
該源代碼中的函數(shù)會(huì)返回配置在棧上的Block。即當(dāng)程序執(zhí)行從該函數(shù)返回函數(shù)調(diào)用方時(shí),變量作用域結(jié)束,因此棧上的Block也被廢棄。雖然有這樣的問(wèn)題,但該源代碼通過(guò)對(duì)應(yīng)ARC的編譯器可轉(zhuǎn)換如下:
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
理解:
- ARC有效時(shí),blk_t tmp 相當(dāng)于blk_t __strong tmp.
- objc_retainBlock實(shí)際上是Block_copy函數(shù)。
詳細(xì)的注釋?zhuān)?/li>
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
/*
* 將通過(guò)Block語(yǔ)法生成的Block(即配置在棧上的Block結(jié)構(gòu)體實(shí)例)
* 賦值給相當(dāng)于Block類(lèi)型的變量tmp
*/
tmp = _Block_copy(tmp);
/*
* _Block_copy函數(shù)
* 將棧上的Block復(fù)制到堆上
* 復(fù)制后,將堆上的地址作為指針賦值給變量tmp
*/
return objc_autoreleaseReturnValue(tmp);
/*
* 將堆上的Block作為Objective-C對(duì)象
* 注冊(cè)到autoreleasepool中,然后返回該對(duì)象
*/
}
*** 將Block作為函數(shù)返回值返回時(shí),編譯器會(huì)自動(dòng)生成復(fù)制到堆上的代碼。 ***
在少數(shù)情況下,Block從棧上復(fù)制到堆上的代碼的手動(dòng)實(shí)現(xiàn)
如果*** 向方法或函數(shù)的參數(shù)中傳遞Block時(shí) ***,編譯器將不能進(jìn)行判斷,需要使用“copy實(shí)例方法”手動(dòng)復(fù)制。
但是,以下方法或函數(shù)不需要手動(dòng)復(fù)制:
- Cocoa框架的方法且方法名中含有usingBlock等時(shí)
- Grand Central Dispathc 的API
*** 對(duì)Block語(yǔ)法調(diào)用copy方法 ***
- (id)getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects: ^{NSLog(@"blk:%d", val);},
^{NSLog(@"blk:%d", val);}, nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
該源代碼的blk(),即Block在執(zhí)行時(shí)發(fā)生異常,應(yīng)用程序強(qiáng)制結(jié)束。這是由于*** getBlockArray函數(shù)執(zhí)行結(jié)束時(shí),棧上的Block被廢棄的緣故。 *** 此時(shí),編譯器不能判斷是否需要復(fù)制。也可以不讓編譯器進(jìn)行判斷,而使其在所有情況下都能復(fù)制。但將Block從棧上復(fù)制到堆上是相當(dāng)消耗CPU的。當(dāng)Block設(shè)置在棧上也能夠使用時(shí),將Block從棧上復(fù)制到堆上只是在浪費(fèi)CPU資源。因此只在此情形下讓編程人員手動(dòng)進(jìn)行復(fù)制。
對(duì)源代碼修改一下,便可正常運(yùn)行:
- (id)getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk:%d", val);} copy],
[^{NSLog(@"blk:%d", val);} copy], nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
*** 對(duì)Block類(lèi)型變量調(diào)用copy方法 ***
按配置Block的存儲(chǔ)域,使用copy方法產(chǎn)生的復(fù)制效果
表 Block的副本
| Block的類(lèi) | 副本源的配置存儲(chǔ)域 | 復(fù)制效果 |
|---|---|---|
| _NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
| _NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 什么也不做 |
| _NSConcreteMallocBlock | 堆 | 引用計(jì)數(shù)增加 |
不管Block配置在何處,用copy方法復(fù)制都不會(huì)引起任何問(wèn)題。在不確定時(shí)調(diào)用copy方法即可。
在ARC有效時(shí),多次調(diào)用copy方法完全沒(méi)有問(wèn)題
blk = [[[[blk copy] copy] copy] copy];
// 經(jīng)過(guò)多次復(fù)制,變量blk仍然持有Block的強(qiáng)引用,該Block不會(huì)被廢棄。
2.3.5 __block變量存儲(chǔ)域
從“Block存儲(chǔ)域”一節(jié)可知,*** 使用__block變量的Block從棧復(fù)制到堆上時(shí),__block變量也會(huì)受到影響。 ***
表 Block從棧復(fù)制到堆時(shí)對(duì)__block變量產(chǎn)生的影響
| __block變量的配置存儲(chǔ)域 | Block從棧復(fù)制到堆時(shí)的影響 |
|---|---|
| 棧 | 從棧復(fù)制到堆并被Block持 |
| 堆 | 被Block持有 |
*** 在一個(gè)Block中使用__block變量 ***

*** 在多個(gè)Block中使用__block變量 ***

*** Block的廢棄和__block變量的釋放 ***

遺留的問(wèn)題
“Block存儲(chǔ)域”一節(jié)中遺留的問(wèn)題:
- 使用__block變量的結(jié)構(gòu)體成員變量__forwarding的原因
不管__block變量配置在棧上還是在堆上,都能夠正確地訪問(wèn)該變量
正如這句話所訴,通過(guò)Block的復(fù)制,__block變量也會(huì)從棧復(fù)制到堆上。此時(shí)可同時(shí)訪問(wèn)棧上的__block變量和堆上的__block變量。
__block int val = 0; // __block變量
void (^blk)(void) = [^{++val;} copy]; // Block
++val;
blk();
NSLog(@"%d", val);
利用copy方法復(fù)制使用了__block變量的Block語(yǔ)法。此時(shí),Block和__block變量均從棧復(fù)制到堆。
*** 在Block語(yǔ)法表達(dá)式中,使用初始化后的__block變量 ***
^{++val;}
*** 在Block語(yǔ)法表達(dá)式之后,使用與Block無(wú)關(guān)的__block變量 ***
++val;
然而,以上兩種源代碼都可以轉(zhuǎn)換為:
++(val.__forwaring->val);
在變化Block語(yǔ)法的函數(shù)中,該變量val為 *** 復(fù)制到堆上的__block變量的結(jié)構(gòu)體實(shí)例 ,而使用與Block無(wú)關(guān)的變量val,為 復(fù)制前棧上的__block變量的結(jié)構(gòu)體實(shí)例 ***。
但是,棧上的__block變量的結(jié)構(gòu)體實(shí)例(即變量val)在__block變量從棧復(fù)制到堆上時(shí),會(huì)將成員變量__forwarding的值替換為復(fù)制目標(biāo)堆上的__block變量的結(jié)構(gòu)體實(shí)例的地址。

2.3.6 截獲對(duì)象
{
id array = [[NSMutableArray alloc] init];
}
該源代碼生成并持有NSMutableArray類(lèi)的對(duì)象,但是附有__strong修飾符的賦值目標(biāo)(變量array)變量作用域立即就會(huì)結(jié)束,因此對(duì)象被立即釋放并廢棄。
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
} copy]; // 調(diào)用copy方法(Block從棧復(fù)制到堆)
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
變量作用域結(jié)束的同時(shí),變量array被廢棄,其對(duì)NSMutableArray類(lèi)的對(duì)象的強(qiáng)引用失效,因此NSMutableArray類(lèi)的對(duì)象被釋放并廢棄(此處我不確定是否會(huì)被廢棄)。但是,該源代碼運(yùn)行正常,執(zhí)行結(jié)果如下:
array count = 1
array count = 2
array count = 3
這意味著賦值給變量array的NSMutableArray類(lèi)的對(duì)象在Block的執(zhí)行部分超出其變量作用域而存在。
經(jīng)clang轉(zhuǎn)換:
/* Block的結(jié)構(gòu)體 / 函數(shù)部分 */
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array;
/*
理解:
1. 被NSMutableArray類(lèi)對(duì)象并被截獲的自動(dòng)變量array,是附有__strong修飾符的成員變量。在Objective-C中,C語(yǔ)言結(jié)構(gòu)體不能含有附有__strong修飾符的變量。因?yàn)榫幾g器不知道何時(shí)進(jìn)行C語(yǔ)言結(jié)構(gòu)體的初始化和廢棄操作,不能很好地管理內(nèi)存。
2. 但是,Objective-C的運(yùn)行時(shí)庫(kù)能準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時(shí)機(jī),因此Block的結(jié)構(gòu)體即時(shí)含有附有__stong修飾符或__weak修飾符的變量,也可以恰當(dāng)?shù)剡M(jìn)行初始化和廢棄。
*/
// 構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong_array, int flags =0) : array(_array){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 靜態(tài)函數(shù) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj)
{
id __strong array = __cself->array;
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
}
// 靜態(tài)函數(shù) __main_block_copy_0
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){
_Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
/*
理解:
1. __main_block_copy_0函數(shù)使用_Block_object_assign函數(shù)將“對(duì)象類(lèi)型對(duì)象”賦值給Block的結(jié)構(gòu)體成員變量array中并持有該對(duì)象
2. _Block_object_assign函數(shù)調(diào)用“相當(dāng)于ratain實(shí)例方法的函數(shù)”,將“對(duì)象”賦值在對(duì)象類(lèi)型的結(jié)構(gòu)體成員變量中。
*/
}
// 靜態(tài)函數(shù) __main_block_dispose_0
static void __main_block_dispose_0(struct __main_block_impl_0 *src){
_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
/*
理解:
1. __main_block_dispose_0函數(shù)使用_Block_object_dispose函數(shù),釋放賦值在Block的結(jié)構(gòu)體成員變量array中的對(duì)象。
2. _Block_object_dispose函數(shù)調(diào)用相當(dāng)于release實(shí)例方法的函數(shù),釋放賦值在對(duì)象類(lèi)型的結(jié)構(gòu)體成員變量中的對(duì)象。
*/
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
/*
理解:
1. __main_block_copy_0函數(shù)(copy函數(shù))和__main_block_dispose_0函數(shù)(dispose函數(shù))指針被賦值__main_block_desc_0結(jié)構(gòu)體成員變量copy和dispose中,但是在轉(zhuǎn)換后的源代碼中,這些函數(shù)包括使用指針全都沒(méi)有被調(diào)用。
2. 而是,在Block從棧復(fù)制到堆時(shí)以及堆上的Block被廢棄時(shí)會(huì)調(diào)用這些函數(shù)。
*/
/* Block語(yǔ)法,使用Block部分 */
blk_t blk;
{
id __strong array = [[NSMutableArray alloc] init];
blk = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, array, 0x22000000);
blk = [blk copy];
}
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
表 調(diào)用copy函數(shù)和dispose函數(shù)的時(shí)機(jī)
| 函數(shù) | 調(diào)用時(shí)機(jī) |
|---|---|
| copy函數(shù) | 棧上的Block復(fù)制到堆時(shí) |
| dispose函數(shù) | 堆上的Block被廢棄時(shí) |
*** 何時(shí)棧上的Block會(huì)復(fù)制到堆 ***
- 調(diào)用Block的copy實(shí)例方法時(shí)
- Block作為函數(shù)返回值返回時(shí)
- 將Block賦值給附有__strong修飾符id類(lèi)型的類(lèi)或Block類(lèi)型成員變量時(shí)
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時(shí)
在棧上的Block被復(fù)制到堆時(shí),copy函數(shù)被調(diào)用,而在釋放復(fù)制到堆上的Block后,誰(shuí)都不持有Block而被廢棄時(shí),dispose函數(shù)被調(diào)用。正因?yàn)檫@種構(gòu)造,通過(guò)使用附有__strong修飾符的自動(dòng)變量,Block中截獲的對(duì)象才能給超出其變量作用域而存在。
*** 如何區(qū)分copy函數(shù)和dispose函數(shù)的對(duì)象類(lèi)型 ***
表 截獲對(duì)象時(shí)和使用__block變量時(shí)的不同
| 對(duì)象 | BLOCK_FIELD_IS_OBJECT |
|---|---|
| __block變量 | BLOCK_FIELD_IS_BYREF |
通過(guò)BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF參數(shù),區(qū)分copy函數(shù)和dispose函數(shù)的對(duì)象類(lèi)型是對(duì)象還是__block變量。
但是,與copy函數(shù)持有被截獲的對(duì)象,dispose函數(shù)釋放截獲的對(duì)象相同,copy函數(shù)持有所使用的__block變量,dispose函數(shù)釋放所使用的__block。
由此可知,Block中使用的賦值給附有__stong修飾符的自動(dòng)變量的對(duì)象和復(fù)制到堆上的__block變量,由于被堆上的Block所持有,因而可超出其變量作用域而存在。
*** 何種情形下,不調(diào)用Block的copy實(shí)例方法 ***
在Block使用對(duì)象類(lèi)型自動(dòng)變量是,除以下情形外,推薦調(diào)用Block的copy實(shí)例方法:
- Block作為函數(shù)返回值返回時(shí)
- 將Block賦值給附有__strong修飾符id類(lèi)型的類(lèi)或Block類(lèi)型成員變量時(shí)
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時(shí)
2.3.7 __block變量和對(duì)象
*** 附加__strong修飾符的對(duì)象類(lèi)型__block變量和自動(dòng)變量***
__block說(shuō)明符可指定“任何類(lèi)型”的自動(dòng)變量。
下面指定用于賦值Objective-C對(duì)象的id類(lèi)型自動(dòng)變量:
__block id obj = [[NSObject alloc] init];
ARC有效時(shí),id類(lèi)型以及對(duì)象類(lèi)型變量默認(rèn)附加__strong修飾符。
所以,該代碼等同于:
__block id __strong obj = [[NSObject alloc] init];
經(jīng)clang轉(zhuǎn)換:
/* __block變量的結(jié)構(gòu)體部分 */
// 結(jié)構(gòu)體 __Block_byref_obj_0
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose_)(void*);
__strong id obj; // __block變量被追加為成員變量
};
// 靜態(tài)函數(shù) __Block_byref_id_object_copy_131
static void __Block_byref_id_object_copy_131(void *dst, void *src){
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
// 靜態(tài)函數(shù) __Block_byref_id_object_dispose_131
static void __Block_byref_id_object_dispose_131(void *src){
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
/* __block變量聲明部分 */
__Block_byref_obj_0 obj = {
0,
&obj,
0x20000000,
sizeof(__Block_byref_obj_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
[[NSObject alloc] init]
};
在Block中使用“附有__strong修飾符的id類(lèi)型或?qū)ο箢?lèi)型自動(dòng)變量”的情況下,當(dāng)Block從棧復(fù)制到堆時(shí),使用_Block_object_copy函數(shù),持有Block截獲的對(duì)象。當(dāng)堆上的Block被廢棄時(shí),使用_Block_object_dispose函數(shù),釋放Block截獲的對(duì)象。
在__block變量為“附有__strong修飾符的id類(lèi)型或?qū)ο箢?lèi)型自動(dòng)變量”的情形下會(huì)發(fā)生同樣的過(guò)程。當(dāng)__block變量從棧復(fù)制到堆時(shí),使用_Block_object_copy函數(shù),持有賦值給__block變量的對(duì)象。當(dāng)堆上的__block變量被廢棄時(shí),使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對(duì)象。
由此可知,即時(shí)對(duì)象賦值給“復(fù)制到堆上的附有__strong修飾符的對(duì)象類(lèi)型__block變量”中,只要__block變量在堆上繼續(xù)存在,那么該對(duì)象就會(huì)繼續(xù)處于被持有的狀態(tài)。這與在Block中對(duì)象賦值給“附有__strong修飾符的對(duì)象類(lèi)型自動(dòng)變量”相同。
*** 附有__weak修飾符的id類(lèi)型自動(dòng)變量 ***
在Block中使用附有__weak修飾符的id類(lèi)型自動(dòng)變量:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
id __weak weakArray = array;
blk = [^(id obj){
[weakArray addObject:obj];
NSLog(@"weakArray count = %ld", [weakArray count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
執(zhí)行結(jié)果:
weakArray count = 0
weakArray count = 0
weakArray count = 0
該段代碼能夠正常運(yùn)行。這是因?yàn)樵谧兞孔饔糜蚪Y(jié)束時(shí),附有__strong修飾符的自動(dòng)變量array所持有的NSMutableArray類(lèi)對(duì)象會(huì)被釋放被廢棄,而附有__weak修飾符的自動(dòng)變量weakArray由于對(duì)NSMutableArray類(lèi)對(duì)象持有弱引用,此時(shí)nil賦值在自動(dòng)變量weakArray上。
*** 附有__weak修飾符的__block變量 ***
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
__block id __weak blockWeakArray = array;
blk = [^(id obj){
[blockWeakArray addObject:obj];
NSLog(@"blockWeakArray count = %ld", [blockWeakArray count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
執(zhí)行結(jié)果:
blockWeakArray count = 0
blockWeakArray count = 0
blockWeakArray count = 0
這段代碼也能正常運(yùn)行。這是因?yàn)榧磿r(shí)附加了__block說(shuō)明符,在變量作用域結(jié)束時(shí),附有__strong修飾符的自動(dòng)變量array所持有的NSMutableArray類(lèi)對(duì)象會(huì)被釋放被廢棄,而附有__weak修飾符的自動(dòng)變量blockWeakArray由于對(duì)NSMutableArray類(lèi)對(duì)象持有弱引用,此時(shí)nil賦值在自動(dòng)變量blockWeakArray上。
注意
- 由于附有__unsafe_unretained修飾符的變量只不過(guò)與指針相同,所以不管在Block中使用還是附加到__block變量中,也不會(huì)像__strong修飾符或__weak修飾符那樣進(jìn)行處理。在使用附有__unsafe_unretained修飾符的變量時(shí),注意不要通過(guò)懸掛指針訪問(wèn)已被廢棄的對(duì)象,否則程序可能會(huì)崩潰!
- 沒(méi)有設(shè)定__autoreleasing修飾符與Block同時(shí)使用。
- __autoreleasing修飾符與__block說(shuō)明符同時(shí)使用會(huì)產(chǎn)生編譯錯(cuò)誤。
2.3.8 Block循環(huán)引用
*** 如果在Block中使用附有__strong修飾符的對(duì)象類(lèi)型自動(dòng)變量,那么當(dāng)Block從棧復(fù)制到堆時(shí),該對(duì)象為Block所持有。 *** 這樣容易引起*** 循環(huán)引用 ***。
*** 使用Block類(lèi)型成員變量和附有__strong修飾符的self出現(xiàn)循環(huán)引用 ***
typedeft void (^blk_t)(void);
@interface MyObject : 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;
}
編譯該源代碼時(shí),編譯器會(huì)發(fā)出警告,這是因?yàn)槌霈F(xiàn)了循環(huán)引用,從而導(dǎo)致dealloc實(shí)例方法沒(méi)有被調(diào)用。

*** 使用Block類(lèi)型成員變量和附有__weak修飾符的self避免循環(huán)引用 ***
為避免此循環(huán)引用,可聲明附有__weak修飾符的變量,并將self賦值給該變量,然后在Block語(yǔ)法中使用該變量。
- (id)init
{
self = [super init];
id __weak weakSelf = self;
blk_ = ^{NSLog(@"self = %@", weakSelf);};
return self;
}
*** 面向iOS4,使用__unsafe_unretained修飾符 ***
- (id)init
{
self = [super init];
id __unsafe_unretained weakSelf = self;
blk_ = ^{NSLog(@"self = %@", weakSelf);};
return self;
}
面向iOS4,使用__unsafe_unretained修飾符替代__weak修飾符,并且不用擔(dān)心懸掛指針問(wèn)題。
*** Block中沒(méi)有使用self,但是截獲了self ***
@interface MyObject : NSObject
{
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
blk_ = ^{NSLog(@"obj_ = %@", obj_);};
return self;
}
該源代碼,如果編譯,編譯器會(huì)發(fā)出警告(出現(xiàn)循環(huán)引用)。這是因?yàn)锽lock語(yǔ)法中使用的obj_實(shí)際上截獲了self,而對(duì)編譯器來(lái)說(shuō),obj_只不過(guò)是對(duì)象的結(jié)構(gòu)體的成員變量。
blk_ = ^{NSLog(@"obj_ = %@", self->obj_);};
為避免循環(huán)引用,解決方法參考前面。
*** 使用__block變量避免循環(huán)引用 ***
typedeft void (^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
__block id blockSelf = self;
blk_ = ^{
NSLog(@"self = %@", blockSelf);
blockSelf = nil; // 記得清零
};
return self;
}
- (void)execBlock
{
blk_();
}
- (void)dealloc
{
NSLog(@"dealloc");
}
@end
int main()
{
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
該源代碼沒(méi)有引起循環(huán)引用。但是,如果不調(diào)用execBlock實(shí)例方法(即不執(zhí)行賦值給成員變量blk_的Block),便會(huì)循環(huán)引用并引起內(nèi)存泄露。該種循環(huán)引用可參看下面。
*** 使用__block變量不恰當(dāng)會(huì)出現(xiàn)循環(huán)引用 ***
在生成并持有MyObject類(lèi)對(duì)象的狀態(tài)下會(huì)引起以下循環(huán)引用,如下圖:
- MyObject類(lèi)對(duì)象持有Block
- Block持有__block變量
- __block變量持有MyObject類(lèi)對(duì)象

如果不執(zhí)行execBlock實(shí)例方法,就會(huì)持續(xù)該循環(huán)引用從而造成內(nèi)存泄露。
如果只想execBlock實(shí)例方法,Block被執(zhí)行,nil被賦值在__block變量blockSelf中。
blk_ = ^{
NSLog(@"self = %@", blockSelf);
blockSelf = nil; // 記得清零
};
因此,__block變量blockSelf對(duì)MyObject類(lèi)對(duì)象的強(qiáng)引用失效,從而避免了循環(huán)引用,如下圖:
- MyObject類(lèi)對(duì)象持有Block
- Block持有__block變量

*** 使用__block變量避免循環(huán)引用的優(yōu)缺點(diǎn) ***
優(yōu)點(diǎn)
通過(guò)__block變量可控制對(duì)象的持有期間
在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可(不必?fù)?dān)心懸掛指針)
在執(zhí)行Block時(shí)可動(dòng)態(tài)地決定是否將nil或其他對(duì)象賦值在__block變量中,從而避免出現(xiàn)循環(huán)引用。
缺點(diǎn)
為避免循環(huán)引用必須執(zhí)行Block
存在執(zhí)行了Block語(yǔ)法,卻不執(zhí)行Block的路徑時(shí),無(wú)法避免循環(huán)引用。
總結(jié)
若由于Block引發(fā)了循環(huán)引用時(shí),根據(jù)Block的用途選擇使用__block變量、__weak修飾符或__unsafe_unretained修飾符來(lái)避免循環(huán)引用。
2.3.9 copy/release
*** ARC無(wú)效時(shí),需要用copy實(shí)例方法手動(dòng)將Blocl從棧復(fù)制到堆,用release實(shí)例方法來(lái)釋放復(fù)制的Block。 ***
void (^blk_on_heap)(void) = [blk_on_stack copy];
[blk_on_heap release];
*** 只要Block有一次復(fù)制并配置在堆上,就可通過(guò)retain實(shí)例方法持有。***
[blk_on_heap retain];
*** 但是,對(duì)于配置在棧上的Block調(diào)用retain實(shí)例方法則不起任何作用。 ***
[blk_on_stack retain];
該源代碼中,雖然對(duì)賦值給blk_on_stack的棧上的Block調(diào)用了retain實(shí)例方法,但實(shí)際上對(duì)此源代碼不起任何作用。因此,推薦使用copy實(shí)例方法來(lái)持有Block(不用retain實(shí)例方法)。
由于Block是C語(yǔ)言的擴(kuò)展,所以在C語(yǔ)言中也可以使用Block語(yǔ)法。此時(shí)使用“Block_copy函數(shù)”和“Block_release函數(shù)”代替copy/release實(shí)例方法。
void (^blk_on_heap)(void) = Block_copy(blk_on_stack);
Block_release(blk_on_heap);
*** ARC無(wú)效時(shí),__block說(shuō)明符被用來(lái)避免Block中的循環(huán)引用。***
這是由于當(dāng)Block從棧復(fù)制到堆時(shí),若Block使用的變量為附有__block說(shuō)明符的id類(lèi)型或?qū)ο箢?lèi)型的自動(dòng)變量,不會(huì)被retain;若Block使用的變量為沒(méi)有__block說(shuō)明符的id類(lèi)型或?qū)ο箢?lèi)型的自動(dòng)變量,則被retain。
typedeft void (^blk_t)(void);
@interface MyObject : 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;
}
該源代碼無(wú)論ARC有效還是無(wú)效都會(huì)引起循環(huán)引用,Block持有self,self持有Block。
可使用__block變量來(lái)避免出現(xiàn)該問(wèn)題。
- (id)init
{
self = [super init];
__block id blockSelf = self;
blk_ = ^{NSLog(@"self = %@", blockSelf);};
return self;
}
這時(shí),由于Block使用__block變量,所以不會(huì)被retain。
注意
ARC有效時(shí),__block說(shuō)明符和__unsafe_unretained修飾符一起使用,來(lái)解決附有__unsafe_unretained修飾符的自動(dòng)變量不能retain的問(wèn)題。
__block說(shuō)明符在ARC有效無(wú)效時(shí)的用途有很大的區(qū)別,所以,在使用__block說(shuō)明符必須清楚源代碼是在ARC有效還是無(wú)效的情況下編譯。


