前言
需要先知道的
Objective-C 轉 C++的方法
因為需要看Block操作的C++源碼季希,所以需要知道轉換的方法耻警,自己轉過來看一看:
1. 在OC源文件block.m寫好代碼。
2. 打開終端涂屁,cd到block.m所在文件夾。
3. 輸入clang -rewrite-objc block.m
灰伟,就會在當前文件夾內(nèi)自動生成對應的block.cpp文件拆又。
關于幾種變量的特點
c語言的函數(shù)中可能使用的變量:
- 函數(shù)的參數(shù)
- 自動變量(局部變量)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
而且,由于存儲區(qū)域特殊栏账,這其中有三種變量是可以在任何時候以任何狀態(tài)調用的:
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
而其他兩種帖族,則是有各自相應的作用域,超過作用域后挡爵,會被銷毀竖般。
2.1 Blocks概要
2.2.1 什么是Blocks
- Blocks是C語言的擴充功能——“帶有自動變量(即局部變量)的匿名函數(shù)”。
- Blocks提供了類似由C++和oc類生成實例或對象來保持變量值得方法茶鹃,其代碼量與編寫C語言函數(shù)差不多涣雕;
- 使用Blocks可以不聲明C++和oc類浙炼,也沒有使用靜態(tài)變量仁期、靜態(tài)全局變量或全局變量時的問題,僅用編寫C語言函數(shù)的源代碼量即可使用帶有自動變量值的匿名函數(shù)。
2.2 Blocks模式
2.2.1 Block 語法
-
與一般的C語言函數(shù)定義相比湘捎,完整形式的Block語法有兩點不同:
- 沒有函數(shù)名稱(因為是匿名函數(shù))
- 帶有“^”(便于查找)
-
Block表達式完整語法
圖2-1 Block 語法.png -
省略返回值類型的Block語法
圖2-2 Block語法省略返回值類型.png
*省略返回值類型時,如果表達式中有return語句榆骚,就使用該返回值的類型登淘,如果表達式中沒有return語句,就使用void類型流译。表達式中含有多個return語句時逞怨,所以return語句的返回值類型必須相同
-
省略返回值類型和參數(shù)列表的Block語法
圖2-3 Block語法省略返回值類型和參數(shù)列表.png
2.2.2 Block類型變量(即Block變量)
在Block語法下,可將Block語法賦值給聲明為Block類型的變量中(即源代碼中一旦使用Block語法就相當于生成了可賦值給Block類型變量的“值”)福澡〉猓“Block”即指源代碼中的Block語法,也指由Block語法所生成的值竞漾。
- 使用Block語法將Block賦值為Block類型變量眯搭。
int(^blk)(int) = ^(int count){return count + 1;};
//等號左側的代碼表示了這個Block的類型:它接受一個int參數(shù),返回一個int值业岁。
//等號右側的代碼是這個Block的值:它是等號左側定義的block類型的一種實現(xiàn)鳞仙。
- 由Block類型變量向Block類型變量賦值。
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;
- 在函數(shù)參數(shù)中使用Block類型變量可以向函數(shù)傳遞Block笔时。
void func(int (^blk)(int))
{
}
- 在函數(shù)返回值中指定Block類型棍好,可以將Block作為函數(shù)的返回值返回。
int (^func()(int))
{
return ^(int count) {return count + 1;};
}
- 在函數(shù)參數(shù)和返回值中使用Block類型變量時允耿,可以通過typedef為Block類型提供別名借笙,從而起到簡化塊類型變量名的作用。
tupedef int (^blk_t) (int);
- 通過Block類型變量調用Block與C語言通常的函數(shù)調用沒有區(qū)別(例如较锡,在函數(shù)和方法中可以將Block類型變量作為參數(shù))业稼。
- (int) methodUsingBlock:(blk_t )blk rate:(int )rate
{
return blk (rate);
}
- Block類型變量可完全像通常的C語言變量一樣使用(例如,可以使用指向Block類型變量的指針蚂蕴,即Block的指針類型變量)低散。
typedef int (^blk_t) (int);
blk_t blk = ^(int count ) {return count + 1;};
blk_t * blkptr = &blk;
(*blkptr)(10);
2.2.3 截獲自動變量值
Blocks中,Block常量表達式會截獲所使用的自動變量的值(即保存該自動變量的瞬間值)骡楼,從而在執(zhí)行塊時使用熔号。
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;
}
輸出結果:
val = 10;
分析:Blocks中,Block表達式截獲所使用的自動變量的值鸟整,即保存該自動變量的瞬間值引镊,因為block表達式保存了自動變量的值,所以在執(zhí)行block語法后,即使改寫block中使用的自動變量的值也不會影響B(tài)lock執(zhí)行時自動變量的值弟头。
2.2.4 __block說明符(即存儲類型修改符)
使用附有__block說明符的自動變量可在Block中賦值吩抓,該變量稱為__block變量。
2.2.5 截獲的自動變量
- 如果將值賦值給Block中截獲的自動變量亮瓷,就會產(chǎn)生編譯錯誤琴拧。這種情況下,需要給截獲的自動變量附加__block說明符嘱支。
- 截獲Objective-C對象蚓胸,調用變更該對象的方法并不會產(chǎn)生編譯錯誤,但是除师,向截獲的自動變量(即所截獲的Objective-C對象)賦值則會產(chǎn)生錯誤沛膳。總之汛聚,賦值給截獲的自動變量會產(chǎn)生編譯錯誤锹安,但使用截獲的值卻不會有任何問題。
- 在現(xiàn)在的Block中倚舀,截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲叹哭,但是,使用指針可以解決該問題痕貌。
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
blk();
2.3 Blocks的實現(xiàn)
2.3.1 Block的實質
- 總結:表層實質是一種類型风罩,深層實質是一種oc對象;
- 通過支持Block的編譯器舵稠,含有Block語法的源代碼轉換為一般C語言編譯器能夠處理的源代碼超升,并作為極為普通的C語言源代碼被編譯。
- 這不過是概念上的問題哺徊,在實際編譯時無法轉換成我們能夠理解的源代碼室琢,Clang(LLVM編譯器)具有將含有Block語法的源代碼轉換為我們可讀源代碼的功能。通過“-rewrite-objc”選項就能將含有Block語法的源代碼變換為C++的源代碼(本質是使用了struct結構的C語言源代碼)落追。
int main()
{
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
通過clang轉換為以下形式:
// 結構體 __block_impl
struct __block_impl {
void *isa;
int Flags; // 標志
int Reserved; // 今后版本升級所需的區(qū)域
void *FuncPtr; // 函數(shù)指針
};
// 結構體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 該結構體的構造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
// _NSConcreteStackBlock用于初始化__block_impl結構體的isa成員
// (將Block指針賦值給Block的結構體成員變量isa)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 最初的源代碼中的Block語法經(jīng)clang變換盈滴,被處理成簡單的C語言函數(shù)(該函數(shù)以Block語法所屬的函數(shù)名——main和該Block語法在該函數(shù)出現(xiàn)的順序值——0來命名)。
// __ceself為指向Block值的變量轿钠。
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
// 靜態(tài)結構體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved; // 今后版本升級所需的區(qū)域
unsigned long Block_size; // Block的大小
} __mian_block_desc_0_DATA = { // 該結構體實例的初始化部分
0,
sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結構體實例)的大小進行初始化
};
// main函數(shù),從這里開始閱讀源代碼
int main()
{
// 調用結構體__main_block_impl_0的構造函數(shù)__main_block_impl_0
void (*blk)(void) =
(void (*)(void)) & __main_block_impl_0(
(void *)__main_block_func_0, &__mian_block_desc_0_DATA);
/*
去掉轉換部分铅乡,如下:
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;
這段代碼對應:
void (^blk)(void) = ^{printf("Block\n");};
理解:
1. 將__main_block_impl_0結構體類型的自動變量(即棧上生成的__main_block_impl_0結構體實例的指針)賦值給__main_block_impl_0結構體指針類型的變量blk
2. __main_block_func_0是由Block語法轉換的C語言函數(shù)指針妹卿。
3. __main_block_desc_0_DATA作為靜態(tài)全局變量初始化的__main_block_desc_0結構體實例指針
4. 將__main_block_impl_0的__block_impl進行展開陷寝,__main_block_impl_0結構體根據(jù)構造函數(shù)會像下面進行初始化:
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);
/*
去掉轉換部分,如下:
(*blk->impl.FuncPtr)(blk);
這段代碼對應:
blk();
理解:
1. 使用函數(shù)指針調用函數(shù)
2. 由Block語法轉換的__main_block_func_0函數(shù)的指針被賦值成員變量FuncPtr中
3. __main_block_func_0函數(shù)的參數(shù)__cself指向Block值,在調用該函數(shù)的源代碼中可以看出Block正是作為參數(shù)進行了傳遞
*/
return 0;
}
OC類和對象的實質(Block就是oc對象)
- 在弄清楚Block就是Objective-C對象前蹈垢,要先理解objc_object結構體和objc_class結構體岩馍。
- id類型是objc_object結構體的指針類型驹尼。
typedef struct objc_object {
Class isa;
} *id;
- Class是objc_class結構體的指針類型地啰。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
} ;
- objc_object結構體和objc_class結構體歸根到底是各個對象和類的實現(xiàn)中最基本的結構體愁拭。
- 如下,通過一個簡單的MyObject類來說明Objective-C類與對象的實質:
@interface MyObject : NSObject
{
int val0;
int val1;
}
- 基于objc_object結構體,該類的對象的結構體如下:
struct MyObject {
Class isa; // 成員變量isa持有該類的結構體實例指針
int val0; // 原先MyObject類的實例變量val0和val1被直接聲明為成員變量
int val1;
}
理解:
- MyObject類的實例變量val0和val1被直接聲明為對象的成員變量亏吝。
- “Objective-C中由類生成對象”意味著岭埠,像該結構體這樣“生成由該類生成的對象的結構體實例”。
- 生成的各個對象(即由該類生成的對象的各個結構體實例)蔚鸥,通過成員變量isa保持該類的結構體實例指針惜论。
各類的結構體是基于objc_class結構體的class_t結構體:
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結構體實例以及NSMutableArray的class_t結構體實例等止喷,均生成并保持各個類的class_t結構體實例馆类。
- 該實例持有聲明的成員變量、方法的名稱弹谁、方法的實現(xiàn)(即函數(shù)指針)蹦掐、屬性以及父類的指針技羔,并被Objective-C運行時庫所使用。
- 回到正題——“Block就是Objective-C對象”卧抗,*** 先看Block結構體:***
struct __main_block_impl_0 {
void *isa;
int Flags; // 標志
int Reserved; // 今后版本升級所需的區(qū)域
void *FuncPtr; // 函數(shù)指針
struct __main_block_desc_0* Desc;
};
理解:
- 此__main_block_impl_0結構體相當于基于objc_object結構體的Objective-C類的對象的結構體。
- 對其中的isa進行初始化鳖粟,如
isa = &_NSConcreteStackBlock;
即_NSConcreteStackBlock相當于class_t結構體實例
- 在將Block作為Objective-C的對象處理時社裆,關于該類的信息放置于_NSConcreteStackBlock中。
2.3.2 獲取自動變量值
截獲自動變量值的源代碼經(jīng)clang轉換向图,如下:
// 結構體 __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// 結構體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt; // Block語法表達式“使用的自動變量”被追加到該結構體
int val;
// 構造函數(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結構體實例(即Block)所截獲的自動變量在Block語法表達式執(zhí)行之前就被聲明定義泳秀,所以,在Objective-C的源代碼中榄攀,執(zhí)行Block語法表達式時無需改動便可使用截獲的自動變量值嗜傅。
2. "截獲自動變量值"意味著在執(zhí)行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構體實例(即Block自身)中檩赢。
3. Block不能直接使用“C語言數(shù)組類型的自動變量”吕嘀,所以,截獲自動變量時贞瞒,會將其值傳遞給結構體的構造函數(shù)進行保存
*/
printf(fmt, val);
}
// 靜態(tài)結構體 __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ù)偶房,從這里開始閱讀源代碼
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
// 調用結構體__main_block_impl_0的構造函數(shù)初始化該結構體實例
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);
/*
理解:
1. 在初始化結構體實例時,會根據(jù)傳遞給構造函數(shù)的參數(shù)對由自動變量追加的成員變量進行初始化(即執(zhí)行Block語法使用的自動變量fmt和val會初始化結構體實例)
2. __main_block_impl_0結構體實例的初始化如下:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
fmt = "val = %d\n";
val = 10;
3. 由上可知军浆,在__main_block_impl_0結構體實例(即Block)中棕洋,自動變量被截獲。
*/
return 0;
}
2.3.3 __block說明符
截獲自動變量值的源代碼經(jīng)clang轉換乒融,如下:
// 結構體 __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// 結構體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt; // Block語法表達式“使用的自動變量”被追加到該結構體
int val;
// 構造函數(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結構體實例(即Block)所截獲的自動變量在Block語法表達式執(zhí)行之前就被聲明定義掰盘,所以,在Objective-C的源代碼中赞季,執(zhí)行Block語法表達式時無需改動便可使用截獲的自動變量值愧捕。
2. "截獲自動變量值"意味著在執(zhí)行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構體實例(即Block自身)中碟摆。
3. Block不能直接使用“C語言數(shù)組類型的自動變量”晃财,所以,截獲自動變量時典蜕,會將其值傳遞給結構體的構造函數(shù)進行保存
*/
printf(fmt, val);
}
// 靜態(tài)結構體 __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ù)断盛,從這里開始閱讀源代碼
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
// 調用結構體__main_block_impl_0的構造函數(shù)初始化該結構體實例
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);
/*
理解:
1. 在初始化結構體實例時,會根據(jù)傳遞給構造函數(shù)的參數(shù)對由自動變量追加的成員變量進行初始化(即執(zhí)行Block語法使用的自動變量fmt和val會初始化結構體實例)
2. __main_block_impl_0結構體實例的初始化如下:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
fmt = "val = %d\n";
val = 10;
3. 由上可知愉舔,在__main_block_impl_0結構體實例(即Block)中钢猛,自動變量被截獲。
*/
return 0;
}
2.3.4 Block存儲域
從“__block說明符”一節(jié)中可知轩缤,*** Block轉換為Block的結構體類型的自動變量,___block變量轉換為__block的結構體類型的自動變量命迈。所謂結構體類型的自動變量贩绕,即棧上生成的該結構體的實例。 ***
表 Block與__block變量的實質
名稱 | 實質 |
---|---|
Block | 棧上block的結構體實例 |
_block變量 | 棧上_block變量的結構體實例 |
從“Block的實質”一節(jié)中可知壶愤,Block是Objective-C對象淑倾,并且該Block的類為_NSConcreteStackBlock。此外征椒,與之類似的還有兩個類:
_NSConcreteStackBlock —— *** 該類對象設置在棧上 ***
_NSConcreteGlobalBlock —— *** 該類對象設置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中 ***
_NSConcreteMallocBlock —— *** 該類對象設置在由malloc函數(shù)分配的內(nèi)存塊(即堆中) ***
在內(nèi)存中的位置如下圖:
注意
- 由于_NSConcreteGlobalBlock類生成的Block對象設置在程序的數(shù)據(jù)區(qū)域中(該類會運用在“全局變量”的聲明中)娇哆,由此該類的結構體實例的內(nèi)容不依賴于執(zhí)行時的狀態(tài),所以整個程序只需一個實例勃救。
- 只在截獲自動變量時碍讨,Block的結構體實例截獲的值才會根據(jù)執(zhí)行時的狀態(tài)變化,而在不截獲自動變量時蒙秒,Block的結構體實例每次截獲的值都相同勃黍。也就是說,即時在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語法時晕讲,只要Block不截獲自動變量覆获,就可以將Block的結構體實例設置在程序的數(shù)據(jù)區(qū)域。
總結
-
Block為_NSConcreteGlobalBlock類對象(即Block配置在程序的數(shù)據(jù)區(qū)域中)的情況有兩種:
- 記述全局變量的地方有Block語法時
- Block語法表達式中不使用“應截獲的自動變量”時
除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象(即類對象設置在棧上)益兄。
而锻梳,_NSConcreteMallocBlock類是Block超出變量作用域可存在的原因。
遺留問題
“__block說明符”一節(jié)中遺留的問題:
- Block超出變量作用域可存在的原因
- __block變量的結構體成員變量__forwarding存在的原因
配置在全局變量的Block净捅,從變量作用域外也可以通過指針安全的使用疑枯,而配置在棧上的Block,如果其所屬的變量作用域結束蛔六,該Block就被廢棄荆永。如圖:
為此,Blocks提供了將Block和__block變量從棧上復制到堆上的方法來解決這個問題国章。
如圖:
實現(xiàn)機制:
- 復制到堆上的Block將_NSConcreteMallocBlock類對象寫入Block的結構體實例的成員變量isa具钥。
impl.isa = &_NSConcreteMallocBlock;
而__block變量的結構體成員變量forwarding可以實現(xiàn)無論變量配置在棧上還是堆上時都能夠正確地訪問__block變量。
ARC有效時液兽,大多數(shù)情況下Block從棧上復制到堆上的代碼由編譯器實現(xiàn)
實際上骂删,ARC有效時,大多數(shù)編譯器會恰當?shù)剡M行判斷四啰,自動生成將Block從棧上復制到堆上的代碼宁玫。
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
該源代碼中的函數(shù)會返回配置在棧上的Block。即當程序執(zhí)行從該函數(shù)返回函數(shù)調用方時柑晒,變量作用域結束欧瘪,因此棧上的Block也被廢棄。雖然有這樣的問題匙赞,但該源代碼通過對應ARC的編譯器可轉換如下:
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有效時佛掖,blk_t tmp 相當于blk_t __strong tmp.
- objc_retainBlock實際上是Block_copy函數(shù)妖碉。
詳細的注釋:
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
/*
* 將通過Block語法生成的Block(即配置在棧上的Block結構體實例)
* 賦值給相當于Block類型的變量tmp
*/
tmp = _Block_copy(tmp);
/*
* _Block_copy函數(shù)
* 將棧上的Block復制到堆上
* 復制后,將堆上的地址作為指針賦值給變量tmp
*/
return objc_autoreleaseReturnValue(tmp);
/*
* 將堆上的Block作為Objective-C對象
* 注冊到autoreleasepool中芥被,然后返回該對象
*/
}
*** 將Block作為函數(shù)返回值返回時欧宜,編譯器會自動生成復制到堆上的代碼。 ***
在少數(shù)情況下拴魄,Block從棧上復制到堆上的代碼的手動實現(xiàn)
如果*** 向方法或函數(shù)的參數(shù)中傳遞Block時 ***鱼鸠,編譯器將不能進行判斷,需要使用“copy實例方法”手動復制羹铅。
但是,以下方法或函數(shù)不需要手動復制:
- Cocoa框架的方法且方法名中含有usingBlock等時
- Grand Central Dispathc 的API
*** 對Block語法調用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í)行時發(fā)生異常职员,應用程序強制結束。這是由于*** getBlockArray函數(shù)執(zhí)行結束時跛溉,棧上的Block被廢棄的緣故焊切。 *** 此時,編譯器不能判斷是否需要復制芳室。也可以不讓編譯器進行判斷专肪,而使其在所有情況下都能復制。但將Block從棧上復制到堆上是相當消耗CPU的堪侯。當Block設置在棧上也能夠使用時嚎尤,將Block從棧上復制到堆上只是在浪費CPU資源。因此只在此情形下讓編程人員手動進行復制伍宦。
對源代碼修改一下芽死,便可正常運行:
- (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();
*** 對Block類型變量調用copy方法 ***
按配置Block的存儲域,使用copy方法產(chǎn)生的復制效果
表 Block的副本
Block的類 | 副本源的配置存儲域 | 復制效果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧復制到堆 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用計數(shù)增加 |
不管Block配置在何處次洼,用copy方法復制都不會引起任何問題关贵。在不確定時調用copy方法即可。
在ARC有效時卖毁,多次調用copy方法完全沒有問題
blk = [[[[blk copy] copy] copy] copy];
// 經(jīng)過多次復制揖曾,變量blk仍然持有Block的強引用,該Block不會被廢棄亥啦。
2.3.5 __block變量存儲域
從“Block存儲域”一節(jié)可知炭剪,*** 使用__block變量的Block從棧復制到堆上時,__block變量也會受到影響禁悠。 ***
表 Block從棧復制到堆時對__block變量產(chǎn)生的影響
__block變量的配置存儲域 | Block從棧復制到堆時的影響 |
---|---|
棧 | 從棧復制到堆并被Block持 |
堆 | 被Block持有 |
*** 在一個Block中使用__block變量 ***
*** 在多個Block中使用__block變量 ***
*** Block的廢棄和__block變量的釋放 ***
遺留的問題
“Block存儲域”一節(jié)中遺留的問題:
- 使用__block變量的結構體成員變量__forwarding的原因
不管__block變量配置在棧上還是在堆上念祭,都能夠正確地訪問該變量
正如這句話所訴,通過Block的復制碍侦,__block變量也會從棧復制到堆上粱坤。此時可同時訪問棧上的__block變量和堆上的__block變量隶糕。
__block int val = 0; // __block變量
void (^blk)(void) = [^{++val;} copy]; // Block
++val;
blk();
NSLog(@"%d", val);
利用copy方法復制使用了__block變量的Block語法。此時站玄,Block和__block變量均從棧復制到堆枚驻。
*** 在Block語法表達式中,使用初始化后的__block變量 ***
^{++val;}
*** 在Block語法表達式之后株旷,使用與Block無關的__block變量 ***
++val;
然而再登,以上兩種源代碼都可以轉換為:
++(val.__forwaring->val);
在變化Block語法的函數(shù)中,該變量val為 *** 復制到堆上的__block變量的結構體實例 晾剖,而使用與Block無關的變量val锉矢,為 復制前棧上的__block變量的結構體實例 ***。
但是齿尽,棧上的__block變量的結構體實例(即變量val)在__block變量從棧復制到堆上時沽损,會將成員變量__forwarding的值替換為復制目標堆上的__block變量的結構體實例的地址。
2.3.6 截獲對象
{
id array = [[NSMutableArray alloc] init];
}
該源代碼生成并持有NSMutableArray類的對象循头,但是附有__strong修飾符的賦值目標(變量array)變量作用域立即就會結束绵估,因此對象被立即釋放并廢棄。
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
} copy]; // 調用copy方法(Block從棧復制到堆)
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
變量作用域結束的同時卡骂,變量array被廢棄国裳,其對NSMutableArray類的對象的強引用失效,因此NSMutableArray類的對象被釋放并廢棄(此處我不確定是否會被廢棄)全跨。但是缝左,該源代碼運行正常,執(zhí)行結果如下:
array count = 1
array count = 2
array count = 3
這意味著賦值給變量array的NSMutableArray類的對象在Block的執(zhí)行部分超出其變量作用域而存在螟蒸。
經(jīng)clang轉換:
/* Block的結構體 / 函數(shù)部分 */
// 結構體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array;
/*
理解:
1. 被NSMutableArray類對象并被截獲的自動變量array盒使,是附有__strong修飾符的成員變量。在Objective-C中七嫌,C語言結構體不能含有附有__strong修飾符的變量少办。因為編譯器不知道何時進行C語言結構體的初始化和廢棄操作,不能很好地管理內(nèi)存诵原。
2. 但是英妓,Objective-C的運行時庫能準確把握Block從棧復制到堆以及堆上的Block被廢棄的時機,因此Block的結構體即時含有附有__stong修飾符或__weak修飾符的變量绍赛,也可以恰當?shù)剡M行初始化和廢棄蔓纠。
*/
// 構造函數(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ù)將“對象類型對象”賦值給Block的結構體成員變量array中并持有該對象
2. _Block_object_assign函數(shù)調用“相當于ratain實例方法的函數(shù)”,將“對象”賦值在對象類型的結構體成員變量中吗蚌。
*/
}
// 靜態(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的結構體成員變量array中的對象。
2. _Block_object_dispose函數(shù)調用相當于release實例方法的函數(shù)蚯妇,釋放賦值在對象類型的結構體成員變量中的對象敷燎。
*/
}
// 靜態(tài)結構體 __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結構體成員變量copy和dispose中暂筝,但是在轉換后的源代碼中,這些函數(shù)包括使用指針全都沒有被調用硬贯。
2. 而是焕襟,在Block從棧復制到堆時以及堆上的Block被廢棄時會調用這些函數(shù)。
*/
/* Block語法饭豹,使用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]);
表 調用copy函數(shù)和dispose函數(shù)的時機
函數(shù) | 調用時機 |
---|---|
copy函數(shù) | 棧上的Block復制到堆時 |
dispose函數(shù) | 堆上的Block被廢棄時 |
*** 何時棧上的Block會復制到堆 ***
- 調用Block的copy實例方法時
- Block作為函數(shù)返回值返回時
- 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時
在棧上的Block被復制到堆時鸵赖,copy函數(shù)被調用,而在釋放復制到堆上的Block后拄衰,誰都不持有Block而被廢棄時它褪,dispose函數(shù)被調用。正因為這種構造翘悉,通過使用附有__strong修飾符的自動變量列赎,Block中截獲的對象才能給超出其變量作用域而存在。
*** 如何區(qū)分copy函數(shù)和dispose函數(shù)的對象類型 ***
表 截獲對象時和使用__block變量時的不同
對象 | BLOCK_FIELD_IS_OBJECT |
---|---|
__block變量 | BLOCK_FIELD_IS_BYREF |
通過BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF參數(shù)镐确,區(qū)分copy函數(shù)和dispose函數(shù)的對象類型是對象還是__block變量。
但是饼煞,與copy函數(shù)持有被截獲的對象源葫,dispose函數(shù)釋放截獲的對象相同,copy函數(shù)持有所使用的__block變量砖瞧,dispose函數(shù)釋放所使用的__block息堂。
由此可知,Block中使用的賦值給附有__stong修飾符的自動變量的對象和復制到堆上的__block變量块促,由于被堆上的Block所持有荣堰,因而可超出其變量作用域而存在。
*** 何種情形下竭翠,不調用Block的copy實例方法 ***
在Block使用對象類型自動變量是振坚,除以下情形外,推薦調用Block的copy實例方法:
- Block作為函數(shù)返回值返回時
- 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時
2.3.7 __block變量和對象
*** 附加__strong修飾符的對象類型__block變量和自動變量***
__block說明符可指定“任何類型”的自動變量斋扰。
下面指定用于賦值Objective-C對象的id類型自動變量:
__block id obj = [[NSObject alloc] init];
ARC有效時渡八,id類型以及對象類型變量默認附加__strong修飾符。
所以传货,該代碼等同于:
__block id __strong obj = [[NSObject alloc] init];
經(jīng)clang轉換:
/* __block變量的結構體部分 */
// 結構體 __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類型或對象類型自動變量”的情況下屎鳍,當Block從棧復制到堆時,使用_Block_object_copy函數(shù)问裕,持有Block截獲的對象逮壁。當堆上的Block被廢棄時,使用_Block_object_dispose函數(shù)粮宛,釋放Block截獲的對象窥淆。
在__block變量為“附有__strong修飾符的id類型或對象類型自動變量”的情形下會發(fā)生同樣的過程卖宠。當__block變量從棧復制到堆時,使用_Block_object_copy函數(shù)祖乳,持有賦值給__block變量的對象。當堆上的__block變量被廢棄時眷昆,使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對象作媚。
由此可知帅刊,即時對象賦值給“復制到堆上的附有__strong修飾符的對象類型__block變量”中,只要__block變量在堆上繼續(xù)存在女揭,那么該對象就會繼續(xù)處于被持有的狀態(tài)。這與在Block中對象賦值給“附有__strong修飾符的對象類型自動變量”相同吧兔。
*** 附有__weak修飾符的id類型自動變量 ***
在Block中使用附有__weak修飾符的id類型自動變量:
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í)行結果:
weakArray count = 0
weakArray count = 0
weakArray count = 0
該段代碼能夠正常運行境蔼。這是因為在變量作用域結束時,附有__strong修飾符的自動變量array所持有的NSMutableArray類對象會被釋放被廢棄伺通,而附有__weak修飾符的自動變量weakArray由于對NSMutableArray類對象持有弱引用箍土,此時nil賦值在自動變量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í)行結果:
blockWeakArray count = 0
blockWeakArray count = 0
blockWeakArray count = 0
這段代碼也能正常運行罐监。這是因為即時附加了__block說明符吴藻,在變量作用域結束時,附有__strong修飾符的自動變量array所持有的NSMutableArray類對象會被釋放被廢棄弓柱,而附有__weak修飾符的自動變量blockWeakArray由于對NSMutableArray類對象持有弱引用调缨,此時nil賦值在自動變量blockWeakArray上。
注意
- 由于附有__unsafe_unretained修飾符的變量只不過與指針相同吆你,所以不管在Block中使用還是附加到__block變量中弦叶,也不會像__strong修飾符或__weak修飾符那樣進行處理。在使用附有__unsafe_unretained修飾符的變量時妇多,注意不要通過懸掛指針訪問已被廢棄的對象伤哺,否則程序可能會崩潰!
- 沒有設定__autoreleasing修飾符與Block同時使用。
- __autoreleasing修飾符與__block說明符同時使用會產(chǎn)生編譯錯誤立莉。
2.3.8 Block循環(huán)引用
*** 如果在Block中使用附有__strong修飾符的對象類型自動變量绢彤,那么當Block從棧復制到堆時,該對象為Block所持有蜓耻。 *** 這樣容易引起*** 循環(huán)引用 ***茫舶。
*** 使用Block類型成員變量和附有__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;
}
編譯該源代碼時,編譯器會發(fā)出警告刹淌,這是因為出現(xiàn)了循環(huán)引用饶氏,從而導致dealloc實例方法沒有被調用。
*** 使用Block類型成員變量和附有__weak修飾符的self避免循環(huán)引用 ***
為避免此循環(huán)引用有勾,可聲明附有__weak修飾符的變量,并將self賦值給該變量喊崖,然后在Block語法中使用該變量雇逞。
- (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修飾符眉踱,并且不用擔心懸掛指針問題谈喳。
*** Block中沒有使用self婿禽,但是截獲了self ***
@interface MyObject : NSObject
{
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
blk_ = ^{NSLog(@"obj_ = %@", obj_);};
return self;
}
該源代碼,如果編譯挽绩,編譯器會發(fā)出警告(出現(xiàn)循環(huán)引用)模聋。這是因為Block語法中使用的obj_實際上截獲了self链方,而對編譯器來說工窍,obj_只不過是對象的結構體的成員變量移剪。
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;
}
該源代碼沒有引起循環(huán)引用。但是蓬坡,如果不調用execBlock實例方法(即不執(zhí)行賦值給成員變量blk_的Block)屑咳,便會循環(huán)引用并引起內(nèi)存泄露训枢。該種循環(huán)引用可參看下面。
*** 使用__block變量不恰當會出現(xiàn)循環(huán)引用 ***
在生成并持有MyObject類對象的狀態(tài)下會引起以下循環(huán)引用聪铺,如下圖:
- MyObject類對象持有Block
- Block持有__block變量
- __block變量持有MyObject類對象
如果不執(zhí)行execBlock實例方法锣杂,就會持續(xù)該循環(huán)引用從而造成內(nèi)存泄露元莫。
如果只想execBlock實例方法踱蠢,Block被執(zhí)行,nil被賦值在__block變量blockSelf中赶盔。
blk_ = ^{
NSLog(@"self = %@", blockSelf);
blockSelf = nil; // 記得清零
};
因此撕攒,__block變量blockSelf對MyObject類對象的強引用失效抖坪,從而避免了循環(huán)引用擦俐,如下圖:
- MyObject類對象持有Block
- Block持有__block變量
*** 使用__block變量避免循環(huán)引用的優(yōu)缺點 ***
優(yōu)點
通過__block變量可控制對象的持有期間
在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可(不必擔心懸掛指針)
在執(zhí)行Block時可動態(tài)地決定是否將nil或其他對象賦值在__block變量中,從而避免出現(xiàn)循環(huán)引用埋合。
缺點
為避免循環(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無效時,需要用copy實例方法手動將Blocl從棧復制到堆叉谜,用release實例方法來釋放復制的Block很钓。 ***
void (^blk_on_heap)(void) = [blk_on_stack copy];
[blk_on_heap release];
*** 只要Block有一次復制并配置在堆上履怯,就可通過retain實例方法持有叹洲。***
[blk_on_heap retain];
*** 但是运提,對于配置在棧上的Block調用retain實例方法則不起任何作用。 ***
[blk_on_stack retain];
該源代碼中栈妆,雖然對賦值給blk_on_stack的棧上的Block調用了retain實例方法鳞尔,但實際上對此源代碼不起任何作用寥假。因此糕韧,推薦使用copy實例方法來持有Block(不用retain實例方法)萤彩。
由于Block是C語言的擴展额衙,所以在C語言中也可以使用Block語法窍侧。此時使用“Block_copy函數(shù)”和“Block_release函數(shù)”代替copy/release實例方法伟件。
void (^blk_on_heap)(void) = Block_copy(blk_on_stack);
Block_release(blk_on_heap);
*** ARC無效時斧账,__block說明符被用來避免Block中的循環(huán)引用。***
這是由于當Block從棧復制到堆時习绢,若Block使用的變量為附有__block說明符的id類型或對象類型的自動變量闪萄,不會被retain败去;若Block使用的變量為沒有__block說明符的id類型或對象類型的自動變量圆裕,則被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;
}
該源代碼無論ARC有效還是無效都會引起循環(huán)引用蛋叼,Block持有self,self持有Block狐胎。
可使用__block變量來避免出現(xiàn)該問題晕鹊。
- (id)init
{
self = [super init];
__block id blockSelf = self;
blk_ = ^{NSLog(@"self = %@", blockSelf);};
return self;
}
這時溅话,由于Block使用__block變量飞几,所以不會被retain屑墨。
注意
ARC有效時,__block說明符和__unsafe_unretained修飾符一起使用以躯,來解決附有__unsafe_unretained修飾符的自動變量不能retain的問題寸潦。
__block說明符在ARC有效無效時的用途有很大的區(qū)別见转,所以斩箫,在使用__block說明符必須清楚源代碼是在ARC有效還是無效的情況下編譯。