前言
Block 是 C 語(yǔ)言的擴(kuò)充功能, Apple 在 iOS4 引入了這個(gè)新功能. 一句話形容 Block, 那就是帶有自動(dòng)變量(局部變量)的匿名函數(shù).
在 OC 中實(shí)現(xiàn)代碼如下
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};

在 Block_layout 中有 isa 指針, 所以 Block 在 OC 中是按照對(duì)象來(lái)處理的. 常見(jiàn)的 Block 有 3 種類型, 分別是 _NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock.
研究工具: clang命令
為了研究編譯器的實(shí)現(xiàn)原理, 需要使用 clang 命令. clang 命令可以將 OC 的源碼轉(zhuǎn)換為 C/C++ 語(yǔ)言的代碼.
clang -rewrite-objc 文件名
一. Block 捕獲外界變量的本質(zhì)
C語(yǔ)言中變量分為以下幾種:
- 自動(dòng)變量
- 函數(shù)參數(shù)
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
先來(lái)一段測(cè)試代碼
int global_i = 1;//全局變量
static int static_global_j = 2;//靜態(tài)全局變量
int main(int argc, const char * argv[]) {
static int static_k = 3;//靜態(tài)變量
int val = 4;//自動(dòng)變量
int val2 = 5;
void(^myBlcok)(void) = ^ {
global_i++;
static_global_j++;
static_k++;
//val++;
NSLog(@"中: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
NSLog(@"%p", &val2);
};
global_i++;
static_global_j++;
static_k++;
val++;
NSLog(@"前: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
NSLog(@"%@", myBlcok);
NSLog(@"%p", &val2);
myBlcok();
NSLog(@"后: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
return 0;
}
這段代碼運(yùn)行的時(shí)候會(huì)報(bào)錯(cuò),提示說(shuō)自動(dòng)變量沒(méi)有加上__block修飾.
用 clang 轉(zhuǎn)換的到.cpp文件,源碼如下
int global_i = 1;
static int static_global_j = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_k;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_k = __cself->static_k; // bound by copy
int val = __cself->val; // bound by copy:此處Block僅僅捕獲了val值,并沒(méi)有捕獲val的內(nèi)存地址.所以在這個(gè)函數(shù)中即使重寫(xiě)這個(gè)val的值,依舊無(wú)法改變Block外面val的值.因此OC在編譯層面就杜絕了這種錯(cuò)誤,在Block中無(wú)法改變自動(dòng)變量的值,編譯器會(huì)報(bào)錯(cuò).
global_i++;
static_global_j++;
(*static_k)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_b6_dgrlb0m175gd2m38vy8qgxzw0000gp_T_main_7d7194_mi_0, global_i, static_global_j, (*static_k), val);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
static int static_k = 3;
int val = 4;
void(*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
global_i++;
static_global_j++;
static_k++;
val++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_b6_dgrlb0m175gd2m38vy8qgxzw0000gp_T_main_7d7194_mi_1, global_i, static_global_j, static_k, val);
((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
return 0;
}
首先全局變量 global_i 和靜態(tài)變量 static_global_j 的值增加,是因?yàn)樗麄兪侨值?作用域很廣,所以 Block 捕獲了它們之后,在 Block 里面進(jìn)行 ++ 操作, Block 結(jié)束后,它們的值可以保存下來(lái).
靜態(tài)變量 static_k 和自動(dòng)變量 i
在 __main_block_func_0 中可以看到 靜態(tài)變量 static_k 和 自動(dòng)變量 i 被 Block 從外部捕捉進(jìn)來(lái),成為__main_block_func_0 這個(gè)結(jié)構(gòu)體的成員變量了.
如果 Block 外面有很多變量,但這些變量并不會(huì)在 Block 里面使用到,那么這些變量不會(huì)被 Block 捕獲進(jìn)來(lái).
在__main_block_func_0 中, 自動(dòng)變量 val 雖然被捕獲進(jìn)來(lái)了, 但是是用 __cself->val 訪問(wèn)的. Block 只是獲取了 val 的值,并沒(méi)有獲取到存放 val 的內(nèi)存地址.所以在__main_block_func_0 這個(gè)函數(shù)中改變 val 的值,依舊無(wú)法改變 Block 外部自動(dòng)變量 val 的值.
4種變量中只有 靜態(tài)變量 , 靜態(tài)全局變量 , 全局變量 這3種是可以在 Block 里面被改變值的.
- 靜態(tài)全局變量,全局變量由于作用域的原因,可以在 Block 里面被改變,存儲(chǔ)在全局區(qū)

靜態(tài)變量傳遞給 Block 的是內(nèi)存地址值,所以能在 Block 里面直接改變值.
對(duì)于自動(dòng)變量而言,要想在 Block 里面改變自動(dòng)變量的值,需要在自動(dòng)變量前加上 __block 關(guān)鍵字(改變存儲(chǔ)區(qū)).
小結(jié):
在 Block 中改變變量值有2種方式, 一是傳遞內(nèi)存地址指針到 Block 中, 二是改變存儲(chǔ)區(qū)域.
二. Block 的 copy 和 dispose
在 OC 中, 一般 Block 分為3種, _NSConcreteStackBlock, _NSConcreteMallocBlock, _NSConcreteGlobalBlock.
先來(lái)比較一下區(qū)別,
- _NSConcreteStackBlock
只用到外部局部變量,成員屬性變量,并且沒(méi)有強(qiáng)指針應(yīng)用的 Block 都是 StackBlock. 生命周期由系統(tǒng)管理. - _NSConcreteMallocBlock
有強(qiáng)指針引用或者使用 copy 修飾的成員屬性引用的 Block 會(huì)被復(fù)制一份到堆區(qū)成為 MallocBlock, 沒(méi)有強(qiáng)指針引用即銷毀, 生命周期由開(kāi)發(fā)者控制. - _NSConcreteGlobalBlock
沒(méi)有用到外界變量或者只用到全局變量,靜態(tài)變量的 Block 都是 GlobalBlock , 生命周期從創(chuàng)建開(kāi)始到程序結(jié)束.