手動(dòng)目錄
- 循環(huán)引用
- block的類
- Block的相關(guān)信息
block本質(zhì)
block如何捕獲外界變量逻淌?
__block修飾的本質(zhì)
block 捕獲外部變量的補(bǔ)充- block 堆/棧 轉(zhuǎn)移過(guò)程
匯編分析
源碼分析- block 簽名
- block的三層拷貝
_Block_copy (第一層拷貝)
block_assign (第二層拷貝)
__Block_byref_id_object_copy_131 (第三層拷貝)
_main_block_dispose_0 釋放- Block總結(jié)
- GCD的Block 是否需要weak疟暖?
循環(huán)引用
一般來(lái)說(shuō) 對(duì)于 不會(huì)自動(dòng)release的block 為了避免循環(huán)引用俐巴,一般采用中介者模式
。
比如 __weak typeof(self) weakSelf = self
中介者模式原理:
用weak之前:
self -> block -> self
用weak之后
weakSelf -> self -> block -> weakSelf
但是weakSelf 由弱引用表來(lái)維護(hù)擎鸠,不會(huì)進(jìn)行計(jì)數(shù)器加減劣光。在dealloc的時(shí)候糟把,weakSelf被釋放糊饱, 就打破了循環(huán)引用
所有的block 都可以用weak來(lái)修飾嗎另锋? 答案是否定的:可能會(huì)造成 self被提前釋放
。
@property (nonatomic, strong) NSString *name;
- (void)viewDidLoad {
_name = @"asdasd";
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) strongSelf = weakSelf; // 這一行加不加 都會(huì)造成self被提前釋放
NSLog(@"%@",strongSelf.name);
});
}
- (void)dealloc {
NSLog(@"dealloc");
}
操作:
跳轉(zhuǎn)到這個(gè)界面之后文判,2s內(nèi)進(jìn)行返回戏仓。
返回的時(shí)候會(huì)進(jìn)行正常的dealloc 打印亡鼠,在2s后 打印 NSLog(@"%@",weakSelf.name); 的時(shí)候就出現(xiàn)了問(wèn)題: 打印(null)橄唬。
這是為什么呢:
weakSelf是弱引用,因?yàn)閐ispatch_after 是到時(shí)才將block加入隊(duì)列抗蠢, 在block執(zhí)行之前迅矛,并沒(méi)有進(jìn)行strongSelf對(duì)其進(jìn)行持有潜叛,所有weakSelf會(huì)在dealloc的時(shí)候被釋放钠导, weakSelf、self票堵、_name 都會(huì)被釋放悴势,所以打印就會(huì)出現(xiàn)null措伐。
所以:weak并不是什么地方都可以使用的
。
在看另外一個(gè)例子:
typedef void(^HandleBlock)(void);
@property (nonatomic, copy) HandleBlock block;
// self -> block -> self (這個(gè)self 雖然沒(méi)有被after copy捧存,但是被 self.block copy)
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",self.name);
});
};
self.block();
// strong -> weakSelf -> self -> block -> strong (strong 是臨時(shí)變量昔穴,block任務(wù)執(zhí)行完畢,strong就被釋放)
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
使用strong-weak-dance
解決引用對(duì)象可能被提前釋放的問(wèn)題泳唠。
block的類
block 有幾種笨腥? - 6 種
在源碼 libclosure-74 中列舉出6種block
void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };
其中 上層常用的3種:
__NSGlobalBlock__
(全局)脖母、__NSMallocBlock__
(堆)窥摄、__NSStackBlock__
(棧)
另外三種一般是系統(tǒng)級(jí)別去使用崭放。
我們用代碼來(lái)打印常用的三種:
種類一:全局block(block內(nèi)部不捕獲外部變量) <__NSGlobalBlock__: 0x10696c450>
void(^ block)(void) = ^{
NSLog@(@"a");
};
NSLog(@"block = %@",block);
id globalBlock = ^(NSString *name,NSInteger a){ //這個(gè)也是全局 因?yàn)樗麤](méi)有捕獲外部變量
NSLog(@"globalBlock : name = %@",name);
};
種類二:堆block (block內(nèi)部捕獲臨時(shí)變量) <__NSMallocBlock__: 0x600001dba220>
int a = 0;
void(^ block)(void) = ^{
NSLog(@"a = %@",@(a));
};
NSLog(@"block = %@",block);
種類三:棧block (捕獲外部變量,并在copy之前) <__NSStackBlock__: 0x7ffeed693978>
int a = 0;
NSLog(@"block = %@",^{
NSLog(@"%@",@(a));
});
這里有需要注意的點(diǎn): 上面的int a = 0建峭; a是臨時(shí)變量
1亿蒸、對(duì)于種類二:如果a 是全局變量掌桩、全局靜態(tài)變量波岛、局部靜態(tài)變量,二的類型是 __NSGlobalBlock__
贡蓖。
2煌茬、在捕獲臨時(shí)變量a的時(shí)候坛善,本身是棧block邻眷,
因?yàn)?臨時(shí)變量在棧上耗溜,超過(guò)作用域 會(huì)被銷毀,為了保證數(shù)據(jù)安全燎字,系統(tǒng)是 將其自動(dòng)進(jìn)行copy操作, 會(huì)將其拷貝到堆上笼蛛。這個(gè)時(shí)候蛉鹿,這個(gè)block變成了mallocBlock妖异。
簡(jiǎn)單點(diǎn)說(shuō):
block默認(rèn)是全局變量,但是在捕獲了棧上 的變量(臨時(shí)變量)响逢,那么它是棧block舔亭,但是棧block為了數(shù)據(jù)安全蟀俊,會(huì)自動(dòng)進(jìn)行copy操作,將其變成堆block矛洞。
Block的相關(guān)信息
block本質(zhì)
用代碼進(jìn)行clang (.m 文件也可以缚甩,但是clang出來(lái)的內(nèi)容太長(zhǎng)窑邦,我們用.c文件 操作)
// 創(chuàng)建一個(gè) C File 文件 BlockTest.c
#include <stdio.h>
int main() {
void(^block)(void) = ^{
printf("a");
};
block();
}
使用clang命令:clang -rewrite-objc BlockTest.c
得到以下信息
int main() {
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
// 簡(jiǎn)化我們需要的信息
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); // 構(gòu)造函數(shù)
((void (*)))(block)->FuncPtr)((__block_impl *)block); // 調(diào)用函數(shù)
// 在編譯后的文件中可以找到由以下信息
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
所以block 的本質(zhì) 可以說(shuō)是對(duì)象冈钦,深一層 可以說(shuō)是結(jié)構(gòu)體
(上面已經(jīng)打印了block的類名)
block如何捕獲外界變量?
還是用代碼進(jìn)行clang
int main() {
int a = 10
void(^block)(void) = ^{
printf("a = %d",a);
};
block();
}
// 查看相關(guān)信息
int main() {
int a = 10;
//與上面相比 __main_block_impl_0 多了一個(gè)參數(shù) a
void(*block)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
block)->FuncPtr)((__block_impl *)block);
}
// 結(jié)構(gòu)體中导盅, 多了一個(gè)參數(shù)a
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 自動(dòng)生成一個(gè)變量來(lái)保存外界的變量
__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;
}
};
// 在執(zhí)行代碼中白翻,將a進(jìn)行了copy操作
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy // ------ 在這里 前面的a 和后面的a 地址不同绢片。
printf("a = %d",a);
}
在這里考慮一個(gè)問(wèn)題: a沒(méi)有進(jìn)行__block 修飾的情況下底循,進(jìn)行a ++ 會(huì)是什么結(jié)果?
假設(shè)能編譯通過(guò)的情況下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
a ++; // 在這里進(jìn)行a++
printf("a = %d",a);
}
那么a++ 是對(duì) int a 中的a 進(jìn)行操作熙涤。和外部的a 完全沒(méi)有任何關(guān)系祠挫。a++中的a 是內(nèi)部的a ,在外界沒(méi)有被定義(不能訪問(wèn)內(nèi)部的a)倦炒,所以是不允許的逢唤。
結(jié)論:
如何捕獲變量
在block的結(jié)構(gòu)體中涤浇,生成了一個(gè)新的成員變量去保存外部變量。在沒(méi)有__block修飾的情況下著恩,為何不能進(jìn)行修改外部變量蜻展?
如果在block中進(jìn)行修改屬性,實(shí)質(zhì)上是對(duì)內(nèi)部成員變量的修改伍茄,但是在外部施逾,是無(wú)法當(dāng)問(wèn)結(jié)構(gòu)體中生成的變量。所以無(wú)法修改曹仗。
__block修飾的本質(zhì)
還是用代碼進(jìn)行clang
int main() {
__block int a = 10;
void(^block)(void) = ^{
printf("a = %d",a);
};
block();
}
// 簡(jiǎn)化之后
int main() {
// 結(jié)構(gòu)體的初始化
__Block_byref_a_0 a = {(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
10};
// 注意第三個(gè)參數(shù)傳遞的是一個(gè)指針地址怎茫。
void(*block)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
block)->FuncPtr)((__block_impl *)block);
}
// 生成了一個(gè) 關(guān)于 a 的 __Block_byref_a_0 的結(jié)構(gòu)體
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
// __block修飾后 block的結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 這里進(jìn)行指針拷貝
__Block_byref_a_0 *a = __cself->a; // bound by ref
printf("a = %d",(a->__forwarding->a));
}
結(jié)論:
--block 是生成了一個(gè) block_byref_ 的結(jié)構(gòu)體 用來(lái)保存原有變量的指針 和 值
傳遞給block的是一個(gè)指針轨蛤。
block 捕獲外部變量的補(bǔ)充
這部分內(nèi)容來(lái)源于另一片文章
其中有一部分內(nèi)容講解了 自動(dòng)變量/靜態(tài)變量/靜態(tài)全局變量/全局變量 多種變量在block中捕獲的情況
說(shuō)明:
自動(dòng)變量 --- 臨時(shí)變量(方法內(nèi)部定義的變量)
靜態(tài)變量 --- 方法內(nèi)部定義的static 變量
靜態(tài)全局變量 --- 方法外部定義的static變量
全局變量 ---- 類的成員變量
int a = 10;
static int b = 20;
int main(int argc, char * argv[]) {
static int c = 30;
int d = 40;
__block int e = 50;
void(^block)(void) = ^{
a++; b++; c++; e++;
// d++; 內(nèi)部無(wú)法操作
printf("\na = %d b = %d c = %d d = %d e = %d",a,b,c,d,e);
};
a++; b++; c++; d++; e++;
printf("a = %d b = %d c = %d d = %d e = %d",a,b,c,d,e);
block();
retrun 0;
}
這里的打印結(jié)果
外部 a = 11 b = 21 c = 31 d = 41 e = 51
內(nèi)部 a = 12 b = 22 c = 32 d = 40 e = 52
因?yàn)閐是在外部進(jìn)行++之前就進(jìn)行了值拷貝俱萍,其他的是進(jìn)行指針訪問(wèn)/直接 訪問(wèn)枪蘑,所以都有產(chǎn)生了相應(yīng)的變化岳颇。
clang 轉(zhuǎn)換之后的關(guān)鍵信息
// 簡(jiǎn)化處理
int main() {
static int c = 30;
int d = 40;
__Block_byref_e_0 e = {(void*)0,
(__Block_byref_e_0 *)&e,
0,
sizeof(__Block_byref_e_0),
50};
void(*block)(void) = (&__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
&c,
d,
(__Block_byref_e_0 *)&e,
570425344));
a++; b++; c++; d++; (e.__forwarding->e)++;
printf("a = %d b = %d c = %d d = %d e = %d",a,b,c,d,(e.__forwarding->e));
(block)->FuncPtr)((__block_impl *)block);
}
// block 新結(jié)構(gòu) ??新增了 c(int *)颅湘、d(int)闯参、e(__Block_byref_ *)??
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *c;
int d;
__Block_byref_e_0 *e; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_c, int _d, __Block_byref_e_0 *_e, int flags=0) : c(_c), d(_d), e(_e->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_e_0 *e = __cself->e; // bound by ref
int *c = __cself->c; // bound by copy
int d = __cself->d; // bound by copy
a++; b++; (*c)++; (e->__forwarding->e)++;
printf("\na = %d b = %d c = %d d = %d e = %d",a,b,(*c),d,(e->__forwarding->e));
}
從上面的結(jié)果中可以知道以下信息
1、全局變量(靜態(tài)/非靜態(tài)) 都不參與block內(nèi)部處理新博。 而是
直接訪問(wèn)外部變量
block結(jié)構(gòu)體中沒(méi)有新增a赫悄、b的成員變量
static void __main_block_func_0 中也沒(méi)有對(duì)a馏慨、b的處理,只有對(duì)c倔撞、d樟澜、e的相應(yīng)處理2叮盘、靜態(tài)變量(c)柔吼、臨時(shí)變量(d)丙唧、__block修飾的臨時(shí)變量(e) 都會(huì)被block 編譯進(jìn)結(jié)構(gòu)體,并
進(jìn)行間接訪問(wèn)
-- 指針地址訪問(wèn)/值拷貝3培漏、變化類型
靜態(tài)變量 內(nèi)部 是處理成相應(yīng)的指針 c => int *c
臨時(shí)變量 只是做一個(gè)值存儲(chǔ) d => int d
__block修飾的內(nèi)部變量處理成指針 e => __Block_byref_e_0 *e4胡本、指針地址拷貝/值拷貝
靜態(tài)變量是指針地址拷貝 ----------------內(nèi)部 能對(duì)外部進(jìn)行修改 int *c = __cself->c; // bound by copy
__block 是指針地址拷貝 ----------------內(nèi)部 能對(duì)外部進(jìn)行修改 __Block_byref_e_0 *e = __cself->e; // bound by ref
臨時(shí)變量是值拷貝 ------------------------內(nèi)部不能對(duì)外部進(jìn)行修改 int d = __cself->d; // bound by copy
再換一個(gè)情況 侧甫,block內(nèi)部使用指針
NSMutableArray *arr = [NSMutableArray new];
NSString *a = @"a";
void(^block)(void) = ^{
[arr addObject:@"1"];
// arr = [NSMutableArray new]; 這一行不被允許
};
block();
用clang轉(zhuǎn)換后的結(jié)構(gòu) 類似于上面的 靜態(tài)變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *arr; // ?? 重點(diǎn)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_arr, NSString *_str, int flags=0) : arr(_arr), str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSMutableArray *arr = __cself->arr; // bound by copy // ??這里也是指針拷貝
......
}
block 捕獲變量總結(jié)
- 1 披粟、全局變量 不捕獲,直接訪問(wèn)
- 2惑艇、非全局變量滨巴,對(duì)捕獲的內(nèi)容進(jìn)行拷貝
===》捕獲的指針碰镜,對(duì)指針進(jìn)行拷貝,可以修改指針?biāo)?的內(nèi)容秽荤,不能改變指針指向(---靜態(tài)變量傳遞給Block是內(nèi)存地址值---) ----- 傳入可變數(shù)組柠横,可以改變數(shù)組內(nèi)容牍氛,不能對(duì)數(shù)組重指向。
===》捕獲的值紊扬,對(duì)值進(jìn)行拷貝蜒茄,不能修改這個(gè)值 ----- 傳入 int a檀葛,內(nèi)部只能使用屿聋,不能修改- 3藏鹊、_block 修飾的變量盘寡。生成了一個(gè) block_byref 的結(jié)構(gòu)體 用來(lái)保存原有變量的指針 和 值
傳遞給block的是一個(gè)指針。
block 捕獲的時(shí)機(jī): 并不是在執(zhí)行block的時(shí)候竿痰。
以下代碼是用來(lái)說(shuō)明 捕獲時(shí)機(jī)的
NSMutableArray *arr = [NSMutableArray new]; // 1
// __block NSMutableArray *arr = [NSMutableArray new]; // 1
void(^block)(void) = ^{
[arr addObject:@"1"]; // 2
};
arr = [NSMutableArray new]; // 3
block();
NSLog(@"arr = %@",arr); // 4
// 無(wú)__block修飾的情況
在2位置, arr的地址:2 捕獲了 1 的地址 也就是說(shuō) 外部修改了arr的地址菇曲,內(nèi)部捕獲到的地址并沒(méi)有隨之改變
// 有__block修飾的情況
2 先捕獲了1 的地址常潮, 執(zhí)行 3的時(shí)候喊式, 2的地址隨之改變岔留。 2 其實(shí)是對(duì) 3的arr進(jìn)行操作检柬。 4打印的是 3的地址。
block 堆/棧 轉(zhuǎn)移過(guò)程
匯編分析
-
無(wú)外部變量引用的block
void(^block)(void) = ^{ // ?? 斷點(diǎn)在這 }; block();
到了斷點(diǎn)之后何址,下符號(hào)斷點(diǎn)
_Block_copy
libsystem_blocks.dylib`_Block_copy: -> 0x1867f48c0 <+0>: stp x22, x21, [sp, #-0x30]! // 步驟一: 斷點(diǎn)在這行 0x1867f48c4 <+4>: stp x20, x19, [sp, #0x10] ........ 0x1867f49a4 <+228>: ldp x22, x21, [sp], #0x30 0x1867f49a8 <+232>: ret // 步驟二: 斷點(diǎn)在這行
步驟一:此時(shí)讀寄存器x0
register read x0
并po
打印
步驟二:斷點(diǎn)在步驟二的位置,在進(jìn)行一次打印// 步驟一 打印結(jié)果 (lldb) register read x0 x0 = 0x00000001002e8080 (lldb) po 0x00000001002e8080 <__NSGlobalBlock__: 0x1002e8080> // 步驟二 打印結(jié)果 和步驟一 一致 (lldb) register read x0 x0 = 0x00000001002e8080 (lldb) po 0x00000001002e8080 <__NSGlobalBlock__: 0x1002e8080>
最后結(jié)果都是GlobalBLock
-
有外部變量引用的情況
換一個(gè)有引用外部變量的block在進(jìn)行上述操作int a = 10; void(^block)(void) = ^{ NSLog(@"%d",a); }; block();
寄存器打印結(jié)果
// 步驟一 (lldb) register read x0 x0 = 0x000000016f75bdc8 (lldb) po 0x000000016f75bdc8 <__NSStackBlock__: 0x16f75bdc8> // 步驟二 (lldb) register read x0 x0 = 0x0000000283a3c630 (lldb) po 0x0000000283a3c630 <__NSMallocBlock__: 0x283a3c630>
打印結(jié)果 從 StackBlock 變成了 MallocBlock
源碼分析
在源碼 libclosure-74 中來(lái)找相關(guān)信息
先看block定義的結(jié)構(gòu)
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE // 依賴于 BLOCK_HAS_COPY_DISPOSE (有BLOCK_HAS_COPY_DISPOSE 才會(huì)有這些 信息)
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE // 依賴于 BLOCK_HAS_SIGNATURE (有BLOCK_HAS_SIGNATURE 才會(huì)有這些 信息)
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
基本結(jié)構(gòu)為Block_layout
有沒(méi)有Block_descriptor_2原押、Block_descriptor_3诸衔,取決于 flags。
看flags結(jié)構(gòu)
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime 正在釋放就缆,釋放標(biāo)記违崇,一般常用BLOCK_NEEDS_FREE 做 位與 操作 一同傳入flags 告知該block可釋放
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 存儲(chǔ)引用計(jì)數(shù)的值诊霹,是一個(gè)可選用參數(shù)
BLOCK_NEEDS_FREE = (1 << 24), // runtime 是否有效的標(biāo)志脾还,程序根據(jù)他來(lái)決定是否增加或減少引用計(jì)數(shù)的值
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler 是否擁有拷貝輔助函數(shù)(a copy helper function),決定Block_descriptor_2
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code 是否擁有block C++的析構(gòu)函數(shù)
BLOCK_IS_GC = (1 << 27), // runtime 標(biāo)志是否有垃圾回收 ---- OS X
BLOCK_IS_GLOBAL = (1 << 28), // compiler 標(biāo)志是否是全局block
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE 與BLOCK_HAS_SIGNATURE相對(duì)鄙漏,判斷當(dāng)前block是否有簽名 用于runtime時(shí)動(dòng)態(tài)調(diào)用
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler 是否有簽名
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler 是否有擴(kuò)展 決定Block_descriptor_3
/** block 捕獲外部變量的類型
BLOCK_FIELD_IS_OBJECT = 3, 對(duì)象
BLOCK_FIELD_IS_BLOCK = 7, 是一個(gè)block變量
BLOCK_FIELD_IS_BYREF = 8, __block 修飾的結(jié)構(gòu)體
BLOCK_FIELD_IS_WEAK = 16, __weak 修飾的變量
BLOCK_BYREF_CALLER = 128 處理Block_byref 內(nèi)部對(duì)象內(nèi)存的時(shí)候會(huì)加一個(gè)往外的標(biāo)記怔蚌,配合上面的枚舉提起使用
*/
};
在源碼中看 block_copy 做了那些事情
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) { // 如果內(nèi)存需要自己管理 那么引用計(jì)數(shù)相應(yīng)增加 最開始是0
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) { // 如果是全局 不做任何操作
return aBlock;
}
else { // 如果是棧block 進(jìn)行一次拷貝
// Its a stack block. Make a copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
在Block_copy 中 主要有3個(gè)判斷 ( 操作的內(nèi)容 椅野,后面加了備注 ) 籍胯,其中重點(diǎn)在else 里面
做了2件事:
- 1杖狼、 malloc 申請(qǐng)內(nèi)存(堆區(qū))
struct Block_layout *result = (struct Block_layout *)malloc(aBlock->descriptor->size);
- 2、將原來(lái)的block 進(jìn)行memmove (從棧區(qū) 移動(dòng)到堆區(qū)) 并將結(jié)構(gòu)的相關(guān)信息進(jìn)行更新
result->flags |= BLOCK_NEEDS_FREE | 2; // 標(biāo)記為 needs__free 并邏輯計(jì)數(shù)器為1
result->isa = _NSConcreteMallocBlock; // 從棧block 編程了 堆block
block 簽名
在 方法中有簽名 =====iOS 底層--Class探索和方法執(zhí)行過(guò)程 ---> 3理朋、方法 中有關(guān)于方法簽名的相關(guān)內(nèi)容
在block源碼中也有簽名信息暗挑, - 其簽名 信息在BLOCK_DESCRIPTOR_3中炸裆,依賴于 flags 中的BLOCK_HAS_SIGNATURE鲜屏。 直接lldb打印比較麻煩,需要計(jì)算地址偏移惯殊。
在 這篇文章中找到簡(jiǎn)便方法---- 借助 aspects。
aspects中 簽名信息的方法是私有务热,稍作修改己儒,去掉staic 并在闪湾。h中進(jìn)行申明,這樣才可以外部訪問(wèn)江醇。
NSString *getMethodSignatureTypeEncoding(NSMethodSignature *methodSignature){
NSMutableString *str = @"".mutableCopy;
const char *rtvType = methodSignature.methodReturnType;
[str appendString:[NSString stringWithUTF8String:rtvType]];
for (int i = 0; i < methodSignature.numberOfArguments; i ++) {
const char *type = [methodSignature getArgumentTypeAtIndex:i];
[str appendString:[NSString stringWithUTF8String:type]];
}
return [str copy];
}
void task() {
id globalBlock1 = ^(NSString *str ,NSInteger a,NSArray *arr){
NSLog(@"globalBlock1 : name = %@",str);
};
NSMethodSignature *signature1 = aspect_blockMethodSignature(globalBlock1, NULL);
NSLog(@"%@",getMethodSignatureTypeEncoding(signature1));
}
// 打印結(jié)果 v@?@"NSString"q@"NSArray"
block 簽名信息
v@?@"NSString"q@"NSArray" -- 中間省略了參數(shù)占位長(zhǎng)度
v - 返回值類型 void - 無(wú)
@? - block 的簽名
@"NSString" 第一個(gè)參數(shù) NSString 類型 -- 表示 是一個(gè)NSString 對(duì)象
q 第二個(gè)參數(shù) NSInteger
@"NSArray" 第三 個(gè)參數(shù) NSArray 類型 -- 表示 是一個(gè)NSArray 對(duì)象
回顧方法簽名信息
v 返回值
@ 第一個(gè)參數(shù) 一個(gè)對(duì)象
: 表示 方法(SEL)
block和方法簽名有點(diǎn)區(qū)別
方法簽名 一般不指明是什么類型 只表示是一個(gè)對(duì)象
比如 v@:
block的簽名 參數(shù)中 不僅指明是一個(gè)對(duì)象陶夜,還指出對(duì)象是什么類型
比如 @"NSString"
block的三層拷貝
_Block_copy (第一層拷貝)
這個(gè)在上面棧/堆轉(zhuǎn)移過(guò)程已經(jīng)說(shuō)過(guò)了
block_assign (第二層拷貝)
在上面轉(zhuǎn)換的cpp文件中 還有一個(gè)方法需要注意
__main_block_copy_0
這里調(diào)用到 _Block_object_assign
,我們?cè)谠创a中看看究竟干了什么律适。
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
*dest = object;
break;
default:
break;
}
}
根據(jù)捕獲的對(duì)對(duì)象類型分別做不同的操作。其中關(guān)鍵的一個(gè) BLOCK_FIELD_IS_BYREF
類型---- __block修飾的類型
_Block_byref_copy(object);
static struct Block_byref *_Block_byref_copy1(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
........
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
........
return src->forwarding;
}
關(guān)鍵部分
- 1胳嘲、在堆區(qū)申請(qǐng)一塊和原來(lái)block_byref 相同大小的空間 (struct Block_byref *)malloc(src->size);
- 2了牛、堆區(qū)的指針指向 copy copy->forwarding = copy;
- 3鹰祸、原來(lái)的 block_byref(棧區(qū)) 也指向copy src->forwarding = copy;
所以 __block 修飾的變量才有修改的能力密浑。
__Block_byref_id_object_copy_131 (第三層拷貝)
這個(gè)拷貝是對(duì)__block修飾的對(duì)象進(jìn)行拷貝尔破〗匠模拷貝到 Block_byref_2 -> BlockByrefKeepFunction byref_keep
最終是對(duì)這個(gè)對(duì)象進(jìn)行了 _Block_object_assign 操作耘擂。
struct __Block_byref_str_0 {
void *__isa;
__Block_byref_str_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *str; // ??
};
也就是對(duì) 結(jié)構(gòu)體中的 NSString *str 進(jìn)行memmove(拷貝操作)醉冤。
__main_block_dispose_0 釋放
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
結(jié)構(gòu)很清晰蚁阳,就是對(duì)不同拷貝的類型 進(jìn)行release操作韵吨。
Block總結(jié)
- 1移宅、 __block 做了什么
在編譯時(shí)漏峰,對(duì)__block修飾的對(duì)象轉(zhuǎn)換成一個(gè) block_byref 的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體里面保存了捕獲對(duì)象的指針和值- 2倔喂、為什么__block 修飾的對(duì)象具有修改的能力席噩?
簡(jiǎn)單點(diǎn)說(shuō):__block修飾的對(duì)象被從棧區(qū)拷貝到堆區(qū)贤壁。堆區(qū)是可以有程序員自己去控制、修改馒索。
具體的過(guò)程為: block進(jìn)行了三次memmove(copy)绰上,
1渠驼、block 本身的copy,將 block本身拷貝的堆區(qū) _block_copy.
2疯趟、捕獲的對(duì)象的結(jié)構(gòu)體進(jìn)行memmove(copy)信峻,在這個(gè)過(guò)程中,在堆區(qū)申請(qǐng)內(nèi)存产镐,然后原來(lái)?xiàng)^(qū)的block_byref 指向這塊內(nèi)存踢步,其本身也指向這塊內(nèi)存,都指向同一塊內(nèi)存空間述雾,所以就有了值/地址修改的能力玻孟。
3鳍征、保存的指針進(jìn)行memmove(copy),同時(shí)是按照步驟二進(jìn)行操作匣掸。目的是使對(duì)象可以進(jìn)行值的修改碰酝。
GCD的Block 是否需要weak戴差?
有些需要,有些不需要
- 不需要:
調(diào)度組(dispatch_group_async / dispatch_group_notify)
柵欄(dispatch_barrier_async / dispatch_barrier_sync)
dispatch_async
dispatch_sync
dispatch_after
同步和異步的是有區(qū)別的:
異步是將任務(wù)進(jìn)行包裝,在包裝的過(guò)程中饭入,進(jìn)行(copy)肛真、引用(invoke)、釋放(call_and_release)乾忱。
同步是任務(wù)不進(jìn)行copy窄瘟,對(duì)于調(diào)用者, 只是 “borrows”(借用)氏义,而不是對(duì)調(diào)用者進(jìn)行持有图云。
- 需要:
dispatch_source_timer(計(jì)時(shí)器)竣况。
源碼分析
-
dispatch_async分析
dispatch_async(dispatch_queue_t dq, dispatch_block_t work) { dispatch_continuation_t dc = _dispatch_continuation_alloc(); uintptr_t dc_flags = DC_FLAG_CONSUME; dispatch_qos_t qos; qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags); _dispatch_continuation_async(dq, dc, qos, dc->dc_flags); } ------------------------------ ?關(guān)鍵在這個(gè)方法 _dispatch_continuation_init? DISPATCH_ALWAYS_INLINE static inline dispatch_qos_t _dispatch_continuation_init(dispatch_continuation_t dc, dispatch_queue_class_t dqu, dispatch_block_t work, dispatch_block_flags_t flags, uintptr_t dc_flags) { void *ctxt = _dispatch_Block_copy(work); // 對(duì)任務(wù)進(jìn)行copy dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED; if (unlikely(_dispatch_block_has_private_data(work))) { dc->dc_flags = dc_flags; dc->dc_ctxt = ctxt; return _dispatch_continuation_init_slow(dc, dqu, flags); } dispatch_function_t func = _dispatch_Block_invoke(work) // 引用這個(gè) 任務(wù) if (dc_flags & DC_FLAG_CONSUME) { func = _dispatch_call_block_and_release; // 釋放 } return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags); } ------------------------------ void _dispatch_call_block_and_release(void *block) { void (^b)(void) = block; b(); Block_release(b); }
源碼分析 async 調(diào)用步驟:
1情萤、對(duì)block 進(jìn)行 copy _dispatch_Block_copy(work);
2、對(duì)任務(wù)進(jìn)行引用(invoke) _dispatch_Block_invoke(work)
3紫岩、釋放這個(gè)任務(wù) _dispatch_call_block_and_release
4泉蝌、將block包裝成 dispatch_continuation_t釋放的條件分析
dc_flags 入?yún)? //DC_FLAG_CONSUME = 0x004
dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
====> 拆解: dc_flags = 0x004 | (0x010 | 0x100); = 1001 0100 (276)
dc_flags & DC_FLAG_CONSUME
====> 拆解: 1001 0100 & 0000 0100 = 0x004
條件成立,會(huì)調(diào)用 _dispatch_call_block_and_release
-
dispatch_after 分析
同樣通過(guò)源碼static inline void _dispatch_after(dispatch_time_t when, dispatch_queue_t dq, void *ctxt, void *handler, bool block) { .... dispatch_continuation_t dc = _dispatch_continuation_alloc(); if (block) { _dispatch_continuation_init(dc, dq, handler, 0, 0); } else { _dispatch_continuation_init_f(dc, dq, ctxt, handler, 0, 0); } ..... }
源碼分析:
又回到了 _dispatch_continuation_init 的調(diào)用勋陪,還是和上面一樣诅愚,不過(guò)傳入的參數(shù) dc_flags 參數(shù)是0 劫映,調(diào)用 _dispatch_call_block_and_release 條件不成立
,這只能說(shuō)明:此時(shí)的block是不能被釋放的雌桑,但是延時(shí)執(zhí)行是在指定時(shí)間后祖今,將block添加的相應(yīng)的線程去執(zhí)行。所以:dispatch_after 的block 不是當(dāng)時(shí)釋放耍目,而是指定時(shí)間后再去釋放
邪驮。(這個(gè)地方源碼沒(méi)有分析出來(lái))通過(guò)API說(shuō)明 能找到相應(yīng)信息:
The block to submit. This function performs a Block_copy and Block_release on behalf of the caller. -
dispatch_sync 分析
static inline void _dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt, dispatch_function_t func, uintptr_t dc_flags) { if (likely(dq->dq_width == 1)) { return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags); } .... _dispatch_introspection_sync_begin(dl); _dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG( _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags))); }
源碼全部看下來(lái),沒(méi)有定義block進(jìn)行copy的操作衔掸。
在官方文檔中看到這樣一段描述:
Unlike with `dispatch_async`, no retain is performed on the target queue. Because calls to this function are synchronous, it "borrows" the reference of the caller. Moreover, no `Block_copy` is performed on the block. 大概意思就是: 與dispatch_async不同俺抽,不在目標(biāo)隊(duì)列上執(zhí)行retain。因?yàn)閷?duì)這個(gè)函數(shù)的調(diào)用是同步的振愿,所以它“借用”了調(diào)用者的引用冕末。此外档桃,不在該塊上執(zhí)行塊復(fù)制。
因?yàn)?code>block 不對(duì)調(diào)用者進(jìn)行持有藻肄,它只是“借用”嘹屯,所以不會(huì)造成循環(huán)引用州弟。
-
dispatch_barrier_async(柵欄) 分析
void dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work) { dispatch_continuation_t dc = _dispatch_continuation_alloc(); uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER; dispatch_qos_t qos; qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags); _dispatch_continuation_async(dq, dc, qos, dc_flags); }
dispatch_barrier_async 類似于 dispatch_async , 將block包裝起來(lái)低零。
dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
條件成立,會(huì)調(diào)用 _dispatch_call_block_and_release
- dispatch_group (調(diào)度組) 分析
調(diào)度組分2個(gè):組里面的每個(gè)小任務(wù) 和 組任務(wù)完成之后的notifyvoid dispatch_group_async_f(dispatch_group_t dg, dispatch_queue_t dq, void *ctxt, dispatch_function_t func) { dispatch_continuation_t dc = _dispatch_continuation_alloc(); uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC; dispatch_qos_t qos; qos = _dispatch_continuation_init_f(dc, dq, ctxt, func, 0, dc_flags); _dispatch_continuation_group_async(dg, dq, dc, qos); }
void dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq, dispatch_block_t db) { dispatch_continuation_t dsn = _dispatch_continuation_alloc(); _dispatch_continuation_init(dsn, dq, db, 0, DC_FLAG_CONSUME); _dispatch_group_notify(dg, dq, dsn); }
dispatch_group_async 和 dispatch_group_notify 都是回歸到 _dispatch_continuation_init
根據(jù)傳入的參數(shù) dc_flags
最后的結(jié)果都是回歸調(diào)用 _dispatch_call_block_and_release
block 變量捕獲 補(bǔ)充 2020.09.19
- (void)test {
void (^block)(void) = ^{
NSLog(@"self = %@",self);
};
block();
}
思考:這個(gè)block中的self 是否會(huì)被捕獲啃奴?
答案: 會(huì)气堕。 因?yàn)樵趖est方法中,默認(rèn)是有2個(gè)參數(shù):(self揖膜、_cmd), 所以在這里的self 其實(shí)是一個(gè)臨時(shí)變量壹粟。(具體編譯后的結(jié)構(gòu) 可通過(guò)clang命令驗(yàn)證)趁仙。
那再看這段代碼:
@property (nonatomic, copy) NSString *name; /// 類的一個(gè)屬性
- (void)test {
void (^block)(void) = ^{
NSLog(@"name = %@",_name);
};
block();
}
在這段代碼中垦页, _name 不會(huì)被捕獲,但是 self會(huì)被捕獲
盏袄。 使用 _name 實(shí)際上是 self->_name; 所以self被捕獲 了辕羽。