前言
block在網(wǎng)絡(luò)上的文章也比較多,
本文將開發(fā)中block使用細(xì)節(jié)和block實(shí)現(xiàn)原理結(jié)合起來,
加上個人的理解,
幫助大家更好地理解block和使用block。
問題
- block的意義
- block為什么不能修改外部變量?這里的外部變量又指的是什么?
- 被block引用的對象,引用計數(shù)為何+=2?
- 循環(huán)引用究竟是為何引起的?
- __block 又是什么原理?
- block與copy
等等
block產(chǎn)生的意義
程序始終都要遵循逐行執(zhí)行的原則,
而block 可以理解為 邏輯觸發(fā)執(zhí)行
定時器 可以理解為 時間觸發(fā)執(zhí)行
舉個有點(diǎn)意思的例子:
背景:我現(xiàn)在身處異世界,我有很多酷炫的技能
我現(xiàn)在有這樣一個技能,發(fā)動這個技能,我可以在當(dāng)前這個地方留一個分身并安排好任務(wù),然后我可以繼續(xù)做我自己的事情了,等我再次使用這個技能時,我的分身將開始處理這項(xiàng)任務(wù),處理完成后,我的分身將會消失。
隱含的問題:給分身安排具體任務(wù)的時候,這個任務(wù)在未來是否能夠完成是未知的,因?yàn)槲覀儾恢牢磥頃l(fā)生什么,
代碼亦是如此。
進(jìn)一步理解:程序是嚴(yán)密而又真實(shí)的,所以并不存在什么高科技呀,黑魔法呀~
block其實(shí)就是用程序?qū)崿F(xiàn)了代碼緩存和對象緩存,相應(yīng)的對象緩存在block對象中,
執(zhí)行block,就是把緩存的代碼執(zhí)行一遍,而相應(yīng)的對象的狀態(tài),可能會因?yàn)閳?zhí)行完緩存下來的代碼而發(fā)生變化。
到此,希望你對block會產(chǎn)生了那么一點(diǎn)點(diǎn)的興趣~
依舊還是要從源碼說起
強(qiáng)調(diào):以下講的變量a 默認(rèn)指的是 自動變量auto,不是對象類型
#import <UIKit/UIKit.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
int a = 0;
void (^block)(void) = ^{
NSLog(@"%d",a);
};
block();
return 0;
}
}
將其翻譯成底層c++文件, 一點(diǎn)一點(diǎn)看
main函數(shù)里的代碼
int a = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
這是一大串什么?
不要著急,我們從上到下,從左往右進(jìn)行說明。
首先,block初始化這一行
// 原代碼
void (^block)(void) = ^{
NSLog(@"%d",a);
};
// 翻譯成c++代碼
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
// 簡化后
block = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,a);
void (*block)(void)函數(shù)指針,參數(shù)void,返回值void((void (*)())上面函數(shù)指針的類型,作用是強(qiáng)轉(zhuǎn)&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a))分解來看
分解第一步:__main_block_impl_0 組成
__main_block_impl_0是一個結(jié)構(gòu)體,包含一個構(gòu)造函數(shù)和三個變量
一個普通的結(jié)構(gòu)體類型,一個結(jié)構(gòu)體指針類型,
還有一個和外部的變量a,名稱一樣,類型一樣。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
分解第二步:__main_block_impl_0 的 構(gòu)造函數(shù)
第一個參數(shù)void *fp,傳入的是 &__main_block_func_0
__main_block_func_0是一個靜態(tài)函數(shù),
它的參數(shù)又是__main_block_impl_0這個結(jié)構(gòu)體指針
即 FuncPtr 存儲的是 __main_block_func_0 靜態(tài)函數(shù)地址
impl.FuncPtr = fp;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_9bc6d9_mi_0,a);
}
是不是快要繞暈了呢?
在靜態(tài)函數(shù)__main_block_func_0內(nèi),
先是獲取__main_block_impl_0結(jié)構(gòu)體內(nèi)的變量a,然后打印出來。
從這一點(diǎn)可以看出 靜態(tài)函數(shù)的作用就是寫在block內(nèi)部的代碼的容器和入口。
而__main_block_impl_0結(jié)構(gòu)體內(nèi)的變量a的值來自外部,是在結(jié)構(gòu)體的構(gòu)造函數(shù)內(nèi)進(jìn)行了賦值操作。
得到如下結(jié)論:
獲取到的基礎(chǔ)變量的值在block初始化的時候已經(jīng)確定了,
block外部的變量a在后續(xù)無論做什么操作,都不會影響block內(nèi)部保存的變量a
這就是在block內(nèi)部直接修改自動變量會報錯的原因,
如果這里直接允許修改了,在目前條件下,也僅僅能做到block內(nèi)部的變量a進(jìn)行重新賦值操作,和外部變量a沒有關(guān)系,產(chǎn)生歧義。

當(dāng)然,使用__block修飾的自動變量可以進(jìn)行修改,那么又是什么原理呢?我們先繼續(xù)解讀當(dāng)前的源碼
第二個參數(shù) &__main_block_desc_0
__main_block_desc_0靜態(tài)結(jié)構(gòu)體存儲的是block的基礎(chǔ)信息
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)};
第三個參數(shù)為我們用到的外部的變量a,賦值給結(jié)構(gòu)體內(nèi)的變量a
第四個參數(shù)沒有傳,默認(rèn)為0 (輔助變量,可以忽略,不會影響block的理解)
分解第三步:__main_block_impl_0 中的 __block_impl
存儲的block的信息,相應(yīng)的參數(shù)在構(gòu)造函數(shù)內(nèi)進(jìn)行了賦值操作
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
最后 block初始化代碼和執(zhí)行代碼放在一起看
void (^block)(void) = ^{
NSLog(@"%d",a);
};
// c++代碼
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
// 簡化
block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)
block();
// c++代碼
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// 簡化
block->FuncPtr(block);
block->FuncPtr 指的就是 __main_block_func_0這個函數(shù)的地址,參數(shù)為block本身
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_19f603_mi_0,a);
}
串起來再看block構(gòu)造
block初始化:block 通過 __main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)進(jìn)行初始化,同時生成__main_block_func_0靜態(tài)函數(shù),并將其地址以及其他相關(guān)信息儲存在__block_impl這個結(jié)構(gòu)體成員變量中。
其中,__block_impl這個結(jié)構(gòu)體成員變量是__main_block_impl_0的首地址。
block調(diào)用:block指針指向的是__main_block_impl_0 的首地址,即__block_impl的地址,所以可以強(qiáng)轉(zhuǎn)為(__block_impl *)類型,并訪問其成員FuncPtr,指向的是靜態(tài)函數(shù)地址,并傳入?yún)?shù)__main_block_impl_0,也就是block自己。
| 名稱 | 類型 | 是否隨block內(nèi)容改變 | 生成順序 | 說明 |
|---|---|---|---|---|
| __block_impl | 結(jié)構(gòu)體 | NO | 1 | 底層結(jié)構(gòu)體,屬于__main_block_impl_0成員 |
| __main_block_impl_0 | 結(jié)構(gòu)體 | YES | 2 | 緩存變量/對象,主結(jié)構(gòu)體 |
| __main_block_func_0 | 靜態(tài)函數(shù) | YES | 2 | 緩存代碼,地址存放在__block_impl中 |
該緩存代碼指的是:

總結(jié):
將外部變量/對象的信息緩存在__main_block_impl_0中,
將代碼緩存在靜態(tài)函數(shù)中,
靜態(tài)函數(shù)在緩存代碼的時候需要用到外部變量/對象的信息
執(zhí)行block就是執(zhí)行了該靜態(tài)函數(shù)
如果到此有不理解的地方可能c++基礎(chǔ)較薄弱,百度一下輔助查看
最后
本文說明了block的用意,揭開了黑魔法的初級面紗,細(xì)講了block基礎(chǔ)源碼,解釋了基礎(chǔ)類型的變量為何不能在block內(nèi)部直接修改
后續(xù)會借此基礎(chǔ)之上,繼續(xù)解讀目錄中的問題