Block的底層基本結(jié)構(gòu)
void blockTest()
{
void (^block)(void) = ^{
NSLog(@"Hello World!");
};
block();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blockTest();
}
}
通過clang命令查看編譯器是如何實現(xiàn)Block的蛆橡,在終端輸入clang -rewrite-objc main.m
幽七,然后會在當(dāng)前目錄生成main.cpp
的C++文件,代碼如下:
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
}
static struct __blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};
void blockTest()
{
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockTest();
}
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
下面我們一個一個來看
__blockTest_block_impl_0
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__blockTest_block_impl_0
是Block
的C++實現(xiàn)括儒,是一個結(jié)構(gòu)體,從命名可以看出表示blockTest
中的第一個(0
)Block
。通常包含兩個成員變量__block_impl impl
鱼鼓,__blockTest_block_desc_0* Desc
和一個構(gòu)造函數(shù)。
__block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl也是一個結(jié)構(gòu)體
- *isa:isa指針该编,指向一個類對象迄本,有三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock课竣、_NSConcreteMallocBlock嘉赎,本例中是_NSConcreteStackBlock類型置媳。
- Flags:block 的負載信息(引用計數(shù)和類型信息),按位存儲公条。
- Reserved:保留變量拇囊。
- *FuncPtr:一個指針,指向
Block
執(zhí)行時調(diào)用的函數(shù)靶橱,也就是Block
需要執(zhí)行的代碼塊寥袭。在本例中是__blockTest_block_func_0
函數(shù)。
__blockTest_block_desc_0
static struct __blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};
__blockTest_block_desc_0
是一個結(jié)構(gòu)體关霸,包含兩個成員變量:
- reserved:
Block
版本升級所需的預(yù)留區(qū)空間传黄,在這里為0。 - Block_size:
Block
大小(sizeof(struct __blockTest_block_impl_0))
队寇。
__blockTest_block_desc_0_DATA
是一個__blockTest_block_desc_0
的一個實例尝江。
__blockTest_block_func_0
__blockTest_block_func_0
就是Block
的執(zhí)行時調(diào)用的函數(shù),參數(shù)是一個__blockTest_block_impl_0
類型的指針英上。
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
}
blockTest
void blockTest()
{
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
第一部分炭序,定義Block
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
我們看到block
變成了一個指針,指向一個通過__blockTest_block_impl_0
構(gòu)造函數(shù)實例化的結(jié)構(gòu)體__blockTest_block_impl_0
實例苍日,__blockTest_block_impl_0
在初始化的時候需要兩個個參數(shù):
-
__blockTest_block_func_0
:Block
塊的函數(shù)指針惭聂。 -
__blockTest_block_desc_0_DATA
:作為靜態(tài)全局變量初始化__main_block_desc_0
的結(jié)構(gòu)體實例指針。
第二部分相恃,調(diào)用Block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
通過block->FuncPtr
指針找到__blockTest_block_func_0
函數(shù)并且轉(zhuǎn)成(void (*)(__block_impl *))
類型辜纲。
((__block_impl *)block)
然后將block
作為參數(shù)傳給這個函數(shù)調(diào)用。
Flags
在__block_impl
中我們看到Flags
拦耐,現(xiàn)在來詳細講一講耕腾。
在這里Block_private.h可以看到Flags
的具體信息:
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
引用淺談 block(1) - clang 改寫后的 block 結(jié)構(gòu)的解釋:
也就是說,一般情況下杀糯,一個
block
的 flags 成員默認(rèn)設(shè)置為 0扫俺。如果當(dāng)block
需要Block_copy()
和Block_release
這類拷貝輔助函數(shù),則會設(shè)置成1 << 25
固翰,也就是BLOCK_HAS_COPY_DISPOSE
類型狼纬。可以搜索到大量講述Block_copy
方法的博文骂际,其中涉及到了BLOCK_HAS_COPY_DISPOSE
疗琉。
總結(jié)一下枚舉類的用法,前 16 位即起到標(biāo)記作用歉铝,又可記錄引用計數(shù):
- BLOCK_DEALLOCATING:釋放標(biāo)記盈简。一般常用 BLOCK_NEEDS_FREE 做 位與 操作,一同傳入 Flags ,告知該 block 可釋放柠贤。
- BLOCK_REFCOUNT_MASK:一般參與判斷引用計數(shù)香浩,是一個可選用參數(shù)。
- BLOCK_NEEDS_FREE:通過設(shè)置該枚舉位种吸,來告知該 block 可釋放弃衍。意在說明 block 是 heap block 呀非,即我們常說的 _NSConcreteMallocBlock 坚俗。
- BLOCK_HAS_COPY_DISPOSE:是否擁有拷貝輔助函數(shù)(a copy helper function)。
- BLOCK_HAS_CTOR:是否擁有 block 析構(gòu)函數(shù)(dispose function)岸裙。
- BLOCK_IS_GC:是否啟用 GC 機制(Garbage Collection)猖败。
- BLOCK_HAS_SIGNATURE:與 BLOCK_USE_STRET 相對,判斷是否當(dāng)前 block 擁有一個簽名降允。用于 runtime 時動態(tài)調(diào)用恩闻。
block截獲變量
截獲auto變量值
我們看到直接在block
修改變量會提示錯誤,為什么呢剧董?
void blockTest()
{
int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
num = 20;
block();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blockTest();
}
}
打印結(jié)果是10幢尚,clang改寫后的代碼如下:
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
int num;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_3c2714_mi_0,num);
}
void blockTest()
{
int num = 10;
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, num));
num = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__blockTest_block_impl_0
多了一個成員變量int num;
,再看看構(gòu)造函數(shù)__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)
翅楼,可以看到第三個參數(shù)只是變量的值尉剩,這也就解釋了為什么打印的是10,因為block
截獲的是值毅臊。
使用static修飾變量
void blockTest()
{
static int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
可以在block
內(nèi)部修改變量了理茎,同時打印結(jié)果是20,30管嬉。clang改寫后的代碼如下:
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
int *num;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
int *num = __cself->num; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_0,(*num));
(*num) = 30;
}
void blockTest()
{
static int num = 10;
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, &num));
num = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_1,num);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__blockTest_block_impl_0
多了一個成員變量int *num;
皂林,和上面不同的是,這次block
截獲的是指針蚯撩,所以可以在內(nèi)部通過指針修改變量的值础倍,同時在外部修改變量的值,block
也能"感知到"胎挎。那么為什么之前傳遞指針呢著隆?因為變量是棧上,作用域是函數(shù)blockTest
內(nèi)呀癣,那么有可能變量比block
先銷毀美浦,這時候block
再通過指針去訪問變量就會有問題。而static
修飾的變量不會被銷毀项栏,也就不用擔(dān)心浦辨。
全局變量
int num = 10;
void blockTest()
{
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
打印結(jié)果是20,30。clang改寫后的代碼如下:
int num = 10;
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_1875c6_mi_0,num);
num = 30;
}
非常簡單流酬,在初始化__blockTest_block_impl_0
并沒有把num
作為參數(shù)币厕,__blockTest_block_func_0
中也是直接訪問全局變量。
總結(jié):
變量類型 | 是否捕獲到block內(nèi)部 | 訪問方式 |
---|---|---|
局部auto變量 | 是 | 值傳遞 |
局部static變量 | 是 | 指針傳遞 |
全局變量 | 否 | 直接訪問 |
使用__block修飾變量
void blockTest()
{
__block int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
效果和使用static修飾變量一樣芽腾,clang改寫后的代碼如下:
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num;
};
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_0,(num->__forwarding->num));
(num->__forwarding->num) = 30;
}
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
void (*dispose)(struct __blockTest_block_impl_0*);
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};
void blockTest()
{
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
(num.__forwarding->num) = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_1,(num.__forwarding->num));
}
哇旦装,難受啊兄dei,怎么多出來這么多東西摊滔,沒關(guān)系阴绢,慢慢分析。
__blockTest_block_impl_0
多出來一個成員變量__Block_byref_num_0 *num;
艰躺,我們看到經(jīng)過__block
修飾的變量類型變成了結(jié)構(gòu)體__Block_byref_num_0
呻袭,__blockTest_block_impl_0
多出來一個成員變量__Block_byref_num_0 *num;
,block
捕獲的是__Block_byref_num_0
類型指針腺兴,
__Block_byref_num_0
我們看到__Block_byref_num_0
是一個結(jié)構(gòu)體左电,并且有一個isa
,因此我們可以知道它其實就是一個對象页响。同時還有一個__Block_byref_a_0 *
類型的__forwarding
和num
篓足,num
我們能猜到就是用來保存變量的值,__forwarding
就有一點復(fù)雜了闰蚕,后面慢慢講栈拖。
__blockTest_block_copy_0和__blockTest_block_dispose_0
__blockTest_block_copy_0
中調(diào)用的是_Block_object_assign
,__blockTest_block_dispose_0
中調(diào)用的是_Block_object_dispose
陪腌。
函數(shù) | 調(diào)用時機 |
---|---|
__blockTest_block_copy_0 |
__block 變量結(jié)構(gòu)體實例從椚杩拷貝到堆時 |
__blockTest_block_dispose_0 |
__block 變量結(jié)構(gòu)體實例引用計數(shù)為0時 |
關(guān)于_Block_object_assign
和_Block_object_dispose
更詳細代碼可以在runtime.c 中查看。
BLOCK_FIELD_IS_BYREF
我們看到_Block_object_assign
和_Block_object_dispose
中都有個參數(shù)值為8诗鸭,BLOCK_FIELD_IS_BYREF
類型染簇,什么意思呢?在Block_private.h 中可以查看到:
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
-
BLOCK_FIELD_IS_OBJECT
:OC對象類型 -
BLOCK_FIELD_IS_BLOCK
:是一個block -
BLOCK_FIELD_IS_BYREF
:在棧上被__block
修飾的變量 -
BLOCK_FIELD_IS_WEAK
:被__weak
修飾的變量强岸,只在Block_byref
管理內(nèi)部對象內(nèi)存時使用 -
BLOCK_BYREF_CALLER
:處理Block_byref
內(nèi)部對象內(nèi)存的時候會加的一個額外標(biāo)記(告訴內(nèi)部實現(xiàn)不要進行retain或者copy)
__blockTest_block_desc_0
我們可以看到它多了兩個回調(diào)函數(shù)指針*copy
和*dispose
锻弓,這兩個指針會被賦值為__main_block_copy_0
和__main_block_dispose_0
最后我們看到訪問num
是這樣的:
__Block_byref_num_0 *num = __cself->num; // bound by ref
(num->__forwarding->num) = 30;
下面就講一講為什么要這樣。
Block的內(nèi)存管理
在前面我們講到__block_impl
指向的_NSConcreteStackBlock類型的類對象蝌箍,其實總共有三種類型:
類型 | 存儲區(qū)域 |
---|---|
_NSConcreteStackBlock | 棧 |
_NSConcreteGlobalBlock | 數(shù)據(jù)區(qū) |
_NSConcreteMallocBlock | 堆 |
前面也講到copy
和dispose
青灼,在ARC環(huán)境下,有哪些情況編譯器會自動將棧上的把Block
從棧上復(fù)制到堆上呢妓盲?
Block從棧中復(fù)制到堆 |
---|
調(diào)用Block的copy實例方法時 |
Block作為函數(shù)返回值返回時 |
在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時候 |
將block賦給帶有__strong修飾符的id類型或者Block類型時 |
當(dāng)Bock
從棧中復(fù)制到堆杂拨,__block
也跟著變化:
- 當(dāng)
Block
在棧上時,__block
的存儲域是棧悯衬,__block
變量被棧上的Block
持有弹沽。 - 當(dāng)
Block
被復(fù)制到堆上時,會通過調(diào)用Block
內(nèi)部的copy
函數(shù),copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign
函數(shù)策橘。此時__block
變量的存儲域是堆炸渡,__block
變量被堆上的Block
持有。 - 當(dāng)堆上的
Block
被釋放丽已,會調(diào)用Block
內(nèi)部的dispose
蚌堵,dispose
函數(shù)內(nèi)部會調(diào)用_Block_object_dispose
,堆上的__block
被釋放沛婴。
- 當(dāng)多個棧上的
Block
使用棧上的__block
變量吼畏,__block
變量被棧上的多個Block
持有。 - 當(dāng)
Block0
被復(fù)制到堆上時瘸味,__block
也會被復(fù)制到堆上宫仗,被堆上Block0
持有够挂。Block1
仍然持有棧上的__block
旁仿,原棧上__block
變量的__forwarding
指向拷貝到堆上之后的__block
變量。 - 當(dāng)
Block1
也被復(fù)制到堆上時孽糖,堆上的__block
被堆上的Block0
和Block1
只有枯冈,并且__block
的引用計數(shù)+1。 - 當(dāng)堆上的
Block
都被釋放办悟,__block
變量結(jié)構(gòu)體實例引用計數(shù)為0尘奏,調(diào)用_Block_object_dispose
,堆上的__block
被釋放病蛉。
下圖是描述__forwarding
變化炫加。這也就能解釋__forwarding
存在的意義:
__forwarding 保證在棧上或者堆上都能正確訪問對應(yīng)變量
int main(int argc, char * argv[]) {
int num = 10;
NSLog(@"%@",[^{
NSLog(@"%d",num);
} class]);
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",[block class]);
}
打印結(jié)果:
2019-05-04 18:40:48.470228+0800 BlockTest[35824:16939613] __NSStackBlock__
2019-05-04 18:40:48.470912+0800 BlockTest[35824:16939613] __NSMallocBlock__
我們可以看到第一個Block
沒有賦值給__strong
指針,而第二個Block
沒有賦值給__strong
指針铺然,所以第一個在棧上俗孝,而第二個在堆上。
Block截獲對象
int main(int argc, char * argv[]) {
{
Person *person = [[Person alloc] init];
person.name = @"roy";
NSLog(@"%@",[^{
NSLog(@"%@",person.name);
} class]);
NSLog(@"%@",@"+++++++++++++");
}
NSLog(@"%@",@"------------");
}
打印結(jié)果:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation Person
- (void)dealloc {
NSLog(@"-------dealloc-------");
}
@end
typedef void(^Block)(void);
int main(int argc, char * argv[]) {
{
Person *person = [[Person alloc] init];
person.name = @"roy";
NSLog(@"%@",[^{
NSLog(@"%@",person.name);
} class]);
NSLog(@"%@",@"+++++++++++++");
}
NSLog(@"%@",@"------------");
}
我們看到當(dāng)Block
內(nèi)部訪問了對象類型的auto對象時魄健,如果Block
是在棧上赋铝,將不會對auto對象產(chǎn)生強引用。
auto Strong 對象
typedef void(^Block)(void);
int main(int argc, char * argv[]) {
Block block;
{
Person *person = [[Person alloc] init];
person.name = @"roy";
block = ^{
NSLog(@"%@",person.name);
};
person.name = @"david";
NSLog(@"%@",@"+++++++++++++");
}
NSLog(@"%@",@"------------");
block ();
}
打印結(jié)果是
2019-05-04 17:46:27.083280+0800 BlockTest[33745:16864251] +++++++++++++
2019-05-04 17:46:27.083934+0800 BlockTest[33745:16864251] ------------
2019-05-04 17:46:27.084018+0800 BlockTest[33745:16864251] david
2019-05-04 17:46:27.084158+0800 BlockTest[33745:16864251] -------dealloc-------
我們看到是先打印的david
再調(diào)用Person
的析構(gòu)方法dealloc
沽瘦,在終端輸入clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m -fobjc-arc
革骨,clang在ARC環(huán)境下改寫后的代碼如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我們看到__main_block_impl_0
中的Person *__strong person;
成員變量。
Block
截獲了auto對象析恋,當(dāng)Block
被拷貝到堆上良哲,Block
強引用auto對象,這就能解釋了為什么超出了person
的作用域助隧,person
沒有立即釋放筑凫,當(dāng)Block
釋放之后,會自動去掉對該對象的強引用,該對象就會被釋放了漏健。
auto Weak 對象
typedef void(^Block)(void);
int main(int argc, char * argv[]) {
Block block;
{
Person *person = [[Person alloc] init];
person.name = @"roy";
__weak Person *weakPerson = person;
block = ^{
NSLog(@"%@",weakPerson.name);
};
weakPerson.name = @"david";
NSLog(@"%@",@"+++++++++++++");
}
NSLog(@"%@",@"------------");
block ();
}
打印結(jié)果是
2019-05-04 17:49:38.858554+0800 BlockTest[33856:16869229] +++++++++++++
2019-05-04 17:49:38.859218+0800 BlockTest[33856:16869229] -------dealloc-------
2019-05-04 17:49:38.859321+0800 BlockTest[33856:16869229] ------------
2019-05-04 17:49:38.859403+0800 BlockTest[33856:16869229] (null)
直接在終端輸入clang -rewrite-objc main.m
會報cannot create __weak reference because the current deployment target does not support weak ref
錯誤嚎货。需要用clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m
,-fobjc-arc
代表當(dāng)前是ARC環(huán)境 -fobjc-runtime=macosx-10.13
:代表當(dāng)前運行時環(huán)境蔫浆,缺一不可殖属,clang之后的代碼:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我們看到__main_block_impl_0
中的Person *__weak weakPerson;
成員變量。
總結(jié):
- 當(dāng)
Block
內(nèi)部訪問了對象類型的auto對象時瓦盛,如果Block
是在棧上洗显,將不會對auto對象產(chǎn)生強引用。 - 如果block被拷貝到堆上原环,會調(diào)用
Block
內(nèi)部的copy函數(shù)挠唆,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign
函數(shù),_Block_object_assign
會根據(jù)auto對象的修飾符(__strong
嘱吗,__weak
玄组,__unsafe_unretained
)做出相應(yīng)的操作,當(dāng)使用的是__strong
時谒麦,將會對person
對象的引用計數(shù)加1俄讹,當(dāng)為__weak
時,引用計數(shù)不變绕德。 - 如果
Block
從對上移除患膛,會調(diào)用block內(nèi)部的dispose
函數(shù),內(nèi)部會調(diào)用_Block_object_dispose
函數(shù)耻蛇,這個函數(shù)會自動釋放引用的auto
對象踪蹬。
Block循環(huán)引用
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^block)(void);
- (void)testReferenceSelf;
@end
@implementation Person
- (void)testReferenceSelf {
self.block = ^ {
NSLog(@"self.name = %s", self.name.UTF8String);
};
self.block();
}
- (void)dealloc {
NSLog(@"-------dealloc-------");
}
@end
int main(int argc, char * argv[]) {
Person *person = [[Person alloc] init];
person.name = @"roy";
[person testReferenceSelf];
}
打印結(jié)果是self.name = roy
,Person
的析構(gòu)方法dealloc
并沒有執(zhí)行臣咖,這是典型的循環(huán)引用跃捣,下面我們研究研究為啥會循環(huán)引用。clang改寫后的代碼如下:
struct __Person__testReferenceSelf_block_impl_0 {
struct __block_impl impl;
struct __Person__testReferenceSelf_block_desc_0* Desc;
Person *const __strong self;
__Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, self, 570425344)));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}
我們看到本來Person中testReferenceSelf
方法是沒有參數(shù)的亡哄,但是轉(zhuǎn)成C++之后多出來兩個參數(shù):* self
和_cmd
枝缔,再看看__Person__testReferenceSelf_block_impl_0
中多出來一個成員變量Person *const __strong self;
,因此我們知道Person中block
捕獲了self
蚊惯,block
強引用self
愿卸,同時self
也強引用block
,因此形成循環(huán)引用截型。
Weak解除循環(huán)引用
@implementation Person
- (void)testReferenceSelf {
__weak typeof(self) weakself = self;
self.block = ^ {
__strong typeof(self) strongself = weakself;
NSLog(@"self.name = %s", strongself.name.UTF8String);
};
self.block();
}
- (void)dealloc {
NSLog(@"-------dealloc-------");
}
@end
打印結(jié)果:
2019-05-04 19:27:48.274358+0800 BlockTest[37426:17007507] self.name = roy
2019-05-04 19:27:48.275016+0800 BlockTest[37426:17007507] -------dealloc-------
我們看到Person對象被正常釋放了趴荸,說明不存在循環(huán)引用,為什么呢宦焦?clang改寫后的代碼如下:
struct __Person__testReferenceSelf_block_impl_0 {
struct __block_impl impl;
struct __Person__testReferenceSelf_block_desc_0* Desc;
Person *const __weak weakself;
__Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __weak _weakself, int flags=0) : weakself(_weakself) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
__attribute__((objc_ownership(weak))) typeof(self) weakself = self;
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, weakself, 570425344)));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}
可以看到__Person__testReferenceSelf_block_impl_0
結(jié)構(gòu)體中weakself成員是一個__weak
修飾的Person類型對象发钝,也就是說__Person__testReferenceSelf_block_impl_0
對Person的依賴是弱依賴顿涣。weak修飾變量是在runtime中進行處理的,在Person對象的Dealloc方法中會調(diào)用weak引用的處理方法酝豪,從weak_table中尋找弱引用的依賴對象涛碑,進行清除處理。
最后
好了孵淘,關(guān)于Block就寫到這里了蒲障,花了五一的三天時間解決了一個基礎(chǔ)知識點,如釋重負瘫证,寫的真心累揉阎。
參考文章
淺談 block(1) - clang 改寫后的 block 結(jié)構(gòu)
Objc Block實現(xiàn)分析
(四)Block之 __block修飾符及其存儲域
(三)Block之截獲變量和對象
關(guān)于Block再啰嗦幾句
__block變量存儲域
Block學(xué)習(xí)⑤--block對對象變量的捕獲
淺談Block實現(xiàn)原理及內(nèi)存特性之三: copy過程分析
iOS底層原理總結(jié) - 探尋block的本質(zhì)(一)