Objc Block實現(xiàn)分析
Block在iOS開發(fā)中使用的頻率是很高的,使用的場景包括接口異步數(shù)據(jù)的回調(diào)(AFN)、UI事件的回調(diào)(BlockKits)、鏈?zhǔn)秸{(diào)用(Masonry)、容器數(shù)據(jù)的遍歷回調(diào)(NSArray、NSDictionary),具體的用法以及使用Block的一些坑這里就不一一贅述了,本文會從源代碼的層面分析我們常用的Block的底層實現(xiàn)原理,做到知其然知其所以然。
本文會從如下幾個主題切入,層層遞進分析Block的底層原理,還原Block本來的面目
- 無參數(shù)無返回值,不使用變量的Block分析
- 使用自動變量的Block分析
- 使用__block修飾的變量的Block分析
- Block引用分析
無參數(shù)無返回值,不使用變量的Block分析
有如下的源代碼,創(chuàng)建一個簡單的block,在block做的處理是打印一個字符串,然后執(zhí)行這個block。接下來會從源碼入手對此進行分析:block是如何執(zhí)行的
// 無參數(shù)無返回值的Block分析
int main(int argc, const char * argv[]) {
void (^blk) (void) = ^{
printf("block invoke");
};
blk();
return 0;
}
// 輸出:
block invoke
使用clang -rewrite-objc main.m命令重寫為C++語法的源文件。從重寫后的代碼中看到,一個簡單block定義和調(diào)用的代碼變成了大幾十行,不過該源代碼還算是相對簡單的,我們從main函數(shù)入口逐步的進行分析:
main函數(shù)中創(chuàng)建__main_block_impl_0類型的實例
__main_block_impl_0結(jié)構(gòu)體是什么鬼呢?我們看__main_block_impl_0結(jié)構(gòu)體的實現(xiàn),包含了__block_impl結(jié)構(gòu)體和__main_block_desc_0結(jié)構(gòu)體指針,為了方便,現(xiàn)在頁先不用管__main_block_desc_0結(jié)構(gòu)體,目前他還沒有真正的使用到,后面講到的__block修飾自動變量以及Block對對象的引用關(guān)系,設(shè)計到內(nèi)存的拷貝和釋放的時候會使用到該變量。__block_impl結(jié)構(gòu)體包含了4個成員,為了簡單分析,我們只關(guān)注其中的FuncPtr成員的FuncPtr成員,這個也是最重要的成員,其它的先忽略不計。
__main_block_impl_0實例的初始化參數(shù)
第一個是__main_block_func_0函數(shù)的地址,__main_block_func_0函數(shù)是什么呢,其實就是Block執(zhí)行的方法,可以看到里面的實現(xiàn)就是一個簡單的打印而已,和我們定義在block中的實現(xiàn)一樣的,該參數(shù)用于初始化impl成員的。至于第二個參數(shù)先不管,在這個例子木有用到。
__main_block_impl_0實例的調(diào)用
創(chuàng)建了__main_block_impl_0結(jié)構(gòu)體類型的實例之后,接下來就是獲取到里面的FuncPtr指針指向的方法進行調(diào)用,參數(shù)是__main_block_impl_0結(jié)構(gòu)體類型的實例本身,上一步創(chuàng)建步驟可知,FuncPtr指針是一個函數(shù)指針指向__main_block_func_0函數(shù)的地址,所以這里本質(zhì)上就是__main_block_func_0函數(shù)的調(diào)用,結(jié)構(gòu)是打印字符串"block invoke",一個簡單的block執(zhí)行孫然在代碼量上增加了,其實也不算復(fù)雜。
注意點:
(__block_impl *)blk這里做了一個強制轉(zhuǎn)換,blk是__main_block_impl_0類型的實例的指針,根據(jù)結(jié)構(gòu)體的內(nèi)存布局規(guī)則,該結(jié)構(gòu)體的第一個成員為__block_impl類型,所以可以強制轉(zhuǎn)換為__block_impl *類型。
由上面的分析,可以得出如下的結(jié)論:使用變量的Block調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù)
// 使用`clang -rewrite-objc main.m`命令重寫為C++語法的源文件,對結(jié)果分析如下
// __block_impl結(jié)構(gòu)體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// `__main_block_impl_0`是block的結(jié)構(gòu)體,包含了兩個成員__block_impl類型的impl成員以及__main_block_desc_0類型的Desc成員;一個構(gòu)造方法__main_block_impl_0,構(gòu)造方法中初始化impl成員和Desc成員
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í)行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block invoke");
}
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ù)main
int main(int argc, const char * argv[]) {
// 創(chuàng)建__main_block_impl_0類型的實例,名稱為blk,這里把改實例強制轉(zhuǎn)換為`void(funcPtr *)(void)`型的方法指針,不懂為何,因為在下一步會把該方法指針強制轉(zhuǎn)換為對應(yīng)的結(jié)構(gòu)體,也就是說`void(funcPtr *)(void)`型的方法指針并沒有真實的使用到。
void (*blk) (void) =
((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA));
// blk是`__main_block_impl_0`類型的實例的指針,根絕結(jié)構(gòu)體的內(nèi)存布局規(guī)則,該結(jié)構(gòu)體的第一個成員為`__block_impl` 類型,可以強制轉(zhuǎn)換為`__block_impl *`類型,獲取FuncPtr函數(shù)指針,強制轉(zhuǎn)換為`(void (*)(__block_impl *))`類型的函數(shù)指針,然后執(zhí)行這個函數(shù)指針對應(yīng)的函數(shù),參數(shù)為blk
// 由上面的步驟可知,Block的調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù)
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
使用自動變量的Block分析
有如下的源代碼,創(chuàng)建一個簡單的block,在block做的處理是打印一個字符串,使用到外部的自動變量,然后執(zhí)行這個block。接下來會從源碼入手對此進行分析:block是如何使用外部的自動變量的
// 使用變量的Block分析
// 源代碼
int main(int argc, const char * argv[]) {
int age = 100;
const char *name = "zyt";
void (^blk) (void) = ^{
printf("block invoke age = %d age = %s", age, name);
};
age = 101;
blk();
return 0;
}
// 輸出:
block invoke age = 100 age = zyt
使用clang -rewrite-objc main.m命令重寫為C++語法的源文件。對比上一個的數(shù)據(jù)結(jié)構(gòu)方法和調(diào)用步驟大體上是一樣的,只針對變化的地方進行分析
__main_block_impl_0結(jié)構(gòu)體的變化
增加了兩個成員:int類型的age成員、char*類型的name成員,用于保存外部變量的值,在初始化的時候就會使用自動變量的值初始化這兩個成員的值
調(diào)用的變化
__main_block_func_0函數(shù)的參數(shù)struct __main_block_impl_0 *__cself在這個例子有使用到了,因為兩個自動變量對應(yīng)的值被保存在__main_block_impl_0結(jié)構(gòu)體中了,方法中有使用到這兩個變量,直接從__main_block_impl_0結(jié)構(gòu)體中獲取這兩個值,但是這兩個值是獨立于自動變量的存在了
由上面的分析,可以得出如下的結(jié)論:使用變量的Block調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù),參數(shù)是保存在block的結(jié)構(gòu)體中的,并且保存的值而不是引用
// 使用`clang -rewrite-objc main.m`命令重寫為C++語法的源文件,對結(jié)果分析如下
// __block_impl結(jié)構(gòu)體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// __main_block_impl_0,包含了四個成員__block_impl類型的impl成員、__main_block_desc_0類型的Desc成員、int類型的age成員、char*類型的name成員;一個構(gòu)造方法__main_block_impl_0,構(gòu)造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個改結(jié)構(gòu)體多了兩個成員,用于保存外部變量的值
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
const char *name;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, const char *_name, int flags=0) : age(_age), name(_name) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block執(zhí)行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
const char *name = __cself->name; // bound by copy
printf("block invoke age = %d age = %s", age, name);
}
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ù)main
// 由上面的步驟可知,使用變量的Block調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù),參數(shù)是保存在block的結(jié)構(gòu)體中的,并且保存的值而不是引用
int main(int argc, const char * argv[]) {
int age = 100;
const char *name = "zyt";
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, name));
age = 101;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
使用__block修飾的變量的Block分析
有如下的源代碼,有個__block修飾的int類型的自動變量age,在block和變量age的作用域中分別作了修改,從輸入的結(jié)果看看是兩次都生效了,接下來會從源碼入手對此進行分析:block中是如何處理__block修飾的自動變量,該自動變量的內(nèi)存是如何變化的
// 源代碼
int main(int argc, const char * argv[]) {
__block int age = 100;
const char *name = "zyt";
void (^blk) (void) = ^{
age += 2;
printf("block invoke age = %d age = %s", age, name);
};
age += 1;
blk();
}
// 輸出 :
// block invoke age = 103 age = zyt
使用clang -rewrite-objc main.m命令重寫為C++語法的源文件,對結(jié)果分析如下的注釋,該轉(zhuǎn)換后的代碼做了些許的調(diào)整,包括代碼的縮進和添加了結(jié)構(gòu)體聲明,使得該代碼可以直接運行
// clang改寫后的代碼如下,以下代碼是經(jīng)過調(diào)整可以直接運行的
struct __main_block_desc_0;
// __block_impl結(jié)構(gòu)體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// `__block`修飾的變量對應(yīng)的結(jié)構(gòu)體,里面包含了該變量的原始值也就是age成員,另外還有一個奇怪的`__forwarding`成員,稍后我們會分析它的用處
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
// `__main_block_impl_0`結(jié)構(gòu)體包含了四個成員`__block_impl`類型的impl成員、`__main_block_desc_0`類型的Desc成員、`__Block_byref_age_0 *`類型的age成員、char*類型的name成員;一個構(gòu)造方法`__main_block_impl_0`,構(gòu)造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個結(jié)構(gòu)體的變化是age的類型變?yōu)榱税b自動變量的結(jié)構(gòu)體了
struct __main_block_impl_0 {
__block_impl impl;
__main_block_desc_0* Desc;
const char *name;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp,
struct __main_block_desc_0 *desc,
const char *_name,
__Block_byref_age_0 *_age,
int flags=0) :
name(_name),
age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block執(zhí)行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
const char *name = __cself->name; // bound by copy
(age->__forwarding->age) += 2;
printf("block invoke age = %d age = %s", (age->__forwarding->age), name);
}
// Block拷貝函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}
// Block銷毀函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
// 重寫后的入口函數(shù)main
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
(void*)0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
100};
const char *name = "zyt";
struct __main_block_impl_0 blockImpl =
__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
name,
(__Block_byref_age_0 *)&age,
570425344);
void (*blk) (void) = ((void (*)())&blockImpl);
(age.__forwarding->age) += 1;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
__block修飾自動變量轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖:

該重寫的代碼大部分和上面分析過的例子代碼是類似,發(fā)現(xiàn)增加了兩個處理方法Block拷貝函數(shù)__main_block_copy_0和Block銷毀函數(shù)__main_block_dispose_0,這兩個函數(shù)會保存在__main_block_desc_0結(jié)構(gòu)體的copy和dispose成員中;另外添加了一個__Block_byref_age_0結(jié)構(gòu)體類型用戶處理__block修飾的自動變量。以下針對這兩點從源代碼的角度進行一個分析
__main_block_copy_0方法中調(diào)用到的_Block_object_assign可以在 runtime.c 這里找到 ,主要看下_Block_object_assign方法里面的處理邏輯
- flags參數(shù)值為8,是BLOCK_FIELD_IS_BYREF枚舉對應(yīng)的值,會走到
_Block_byref_assign_copy方法的調(diào)用步驟 -
_Block_byref_assign_copy方法會在在堆上創(chuàng)建Block_byref對象,也就是Block對象,并且把棧上和堆上的Block對象的forwarding屬性值都修改為指向堆上的Block對象,這樣使用兩個對象的修改值都會修改為同一個地方
棧上的__block自動變量__forwarding指向關(guān)系以及拷貝到堆上之后__forwarding指向關(guān)系如下圖所示

具體使用到的代碼和對應(yīng)的注釋如下:
/*
* When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
* to do the assignment.
*/
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
// 代碼會走到這個分支中,調(diào)用方法`_Block_byref_assign_copy`
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
/*
* Runtime entry points for maintaining the sharing knowledge of byref data blocks.
*
* A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
* Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
* We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment it.
* Otherwise we need to copy it and update the stack forwarding pointer
* XXX We need to account for weak/nonretained read-write barriers.
*/
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
//printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
//printf("src dump: %s\n", _Block_byref_dump(src));
if (src->forwarding->flags & BLOCK_IS_GC) {
; // don't need to do any more work
}
else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
//printf("making copy\n");
// src points to stack
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
// copy是拷貝到堆上的Block_byref類型對象,scr是原來的Block_byref類型對象,兩者的forwarding成員都指向到堆上的Block_byref類型對象也就是copy,這樣不管是在棧上修改__block修飾的變量(age.age = 102調(diào)用)還是在堆上修改__block修飾的變量()
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; // mark isa field so it gets weak scanning
}
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
// just bits. Blast 'em using _Block_memmove in case they're __strong
_Block_memmove(
(void *)©->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// assign byref data block pointer into new Block
_Block_assign(src->forwarding, (void **)destp);
}
Block引用分析
Block強引用分析
// 定義類YTObject
@interface YTObject : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^blk)(void);
- (void)testReferenceSelf;
@end
@implementation YTObject
- (void)testReferenceSelf {
self.blk = ^ {
// 這里不管是使用self.name還是_name,從clang重寫的代碼上看,處理方式是一樣的
printf("self.name = %s", self.name.UTF8String);
};
self.blk();
}
- (void)dealloc {
NSLog(@"==dealloc==");
}
@end
// 使用YTObject
int main(int argc, const char * argv[]) {
YTObject *obj = [YTObject new];
obj.name = @"hello";
[obj testReferenceSelf];
return 0;
}
// 輸出 :
// self.name = hello
使用clang -rewrite-objc main.m命令重寫為C++語法的源文件如下
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[6];
} _OBJC_$_INSTANCE_METHODS_YTObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
6,
{{(struct objc_selector *)"testReferenceSelf", "v16@0:8", (void *)_I_YTObject_testReferenceSelf},
{(struct objc_selector *)"dealloc", "v16@0:8", (void *)_I_YTObject_dealloc},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_YTObject_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YTObject_setName_},
{(struct objc_selector *)"blk", "@?16@0:8", (void *)_I_YTObject_blk},
{(struct objc_selector *)"setBlk:", "v24@0:8@?16", (void *)_I_YTObject_setBlk_}}
};
struct __YTObject__testReferenceSelf_block_impl_0 {
struct __block_impl impl;
struct __YTObject__testReferenceSelf_block_desc_0* Desc;
YTObject *self;
__YTObject__testReferenceSelf_block_impl_0(void *fp, struct __YTObject__testReferenceSelf_block_desc_0 *desc, YTObject *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __YTObject__testReferenceSelf_block_func_0(struct __YTObject__testReferenceSelf_block_impl_0 *__cself) {
YTObject *self = __cself->self; // bound by copy
printf("self.name = %s", ((const char * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")), sel_registerName("UTF8String")));
}
static void __YTObject__testReferenceSelf_block_copy_0(struct __YTObject__testReferenceSelf_block_impl_0*dst, struct __YTObject__testReferenceSelf_block_impl_0*src) {
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __YTObject__testReferenceSelf_block_dispose_0(struct __YTObject__testReferenceSelf_block_impl_0*src) {
_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __YTObject__testReferenceSelf_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __YTObject__testReferenceSelf_block_impl_0*, struct __YTObject__testReferenceSelf_block_impl_0*);
void (*dispose)(struct __YTObject__testReferenceSelf_block_impl_0*);
} __YTObject__testReferenceSelf_block_desc_0_DATA = { 0, sizeof(struct __YTObject__testReferenceSelf_block_impl_0), __YTObject__testReferenceSelf_block_copy_0, __YTObject__testReferenceSelf_block_dispose_0};
static void _I_YTObject_testReferenceSelf(YTObject * self, SEL _cmd) {
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlk:"), ((void (*)())&__YTObject__testReferenceSelf_block_impl_0((void *)__YTObject__testReferenceSelf_block_func_0, &__YTObject__testReferenceSelf_block_desc_0_DATA, self, 570425344)));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("blk"))();
}
int main(int argc, const char * argv[]) {
YTObject *obj = ((YTObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YTObject"), sel_registerName("new"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)obj, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_fk_19cr58zj0f7f19001k_mxzlm0000gp_T_main_21b52d_mii_1);
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("testReferenceSelf"));
return 0;
}
Block引用對象轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖:

從類圖上可以明顯的看到__YTObject__testReferenceSelf_block_impl_0和YTObject之間有循環(huán)依賴的關(guān)系,這樣NSLog(@"==dealloc==");這段代碼最終是沒有調(diào)用到,也就是這里會出現(xiàn)Block循環(huán)引用導(dǎo)致內(nèi)存泄漏問題
Block弱引用分析
Block的循環(huán)引用問題其中一種解決方案是可以使用weakself來解除這種強引用關(guān)系,防止內(nèi)存的泄漏,代碼的改造如下
@interface YTObject : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^blk)(void);
- (void)testReferenceSelf;
@end
@implementation YTObject
- (void)testReferenceSelf {
__weak typeof(self) weakself = self;
self.blk = ^ {
__strong typeof(self) strongself = weakself;
// 這里不管是使用self.name還是_name,從clang重寫的代碼上看,處理方式是一樣的
printf("self.name = %s\n", strongself.name.UTF8String);
};
self.blk();
}
- (void)dealloc {
printf("==dealloc==");
}
@end
// 使用YTObject
int main(int argc, const char * argv[]) {
YTObject *obj = [YTObject new];
obj.name = @"hello";
[obj testReferenceSelf];
return 0;
}
添加了weak之后需要使用clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.14 main.mm這個命令才能夠重寫為C++語言對應(yīng)的代碼,重寫后的代碼如下
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[6];
} _OBJC_$_INSTANCE_METHODS_YTObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
6,
{{(struct objc_selector *)"testReferenceSelf", "v16@0:8", (void *)_I_YTObject_testReferenceSelf},
{(struct objc_selector *)"dealloc", "v16@0:8", (void *)_I_YTObject_dealloc},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_YTObject_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YTObject_setName_},
{(struct objc_selector *)"blk", "@?16@0:8", (void *)_I_YTObject_blk},
{(struct objc_selector *)"setBlk:", "v24@0:8@?16", (void *)_I_YTObject_setBlk_}}
};
struct __YTObject__testReferenceSelf_block_impl_0 {
struct __block_impl impl;
struct __YTObject__testReferenceSelf_block_desc_0* Desc;
YTObject *const __weak weakself;
__YTObject__testReferenceSelf_block_impl_0(void *fp, struct __YTObject__testReferenceSelf_block_desc_0 *desc, YTObject *const __weak _weakself, int flags=0) : weakself(_weakself) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __YTObject__testReferenceSelf_block_func_0(struct __YTObject__testReferenceSelf_block_impl_0 *__cself) {
YTObject *const __weak weakself = __cself->weakself; // bound by copy
__attribute__((objc_ownership(strong))) typeof(self) strongself = weakself;
printf("self.name = %s\n", ((const char * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)strongself, sel_registerName("name")), sel_registerName("UTF8String")));
}
static void __YTObject__testReferenceSelf_block_copy_0(struct __YTObject__testReferenceSelf_block_impl_0*dst, struct __YTObject__testReferenceSelf_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakself, (void*)src->weakself, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __YTObject__testReferenceSelf_block_dispose_0(struct __YTObject__testReferenceSelf_block_impl_0*src) {
_Block_object_dispose((void*)src->weakself, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __YTObject__testReferenceSelf_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __YTObject__testReferenceSelf_block_impl_0*, struct __YTObject__testReferenceSelf_block_impl_0*);
void (*dispose)(struct __YTObject__testReferenceSelf_block_impl_0*);
} __YTObject__testReferenceSelf_block_desc_0_DATA = { 0, sizeof(struct __YTObject__testReferenceSelf_block_impl_0), __YTObject__testReferenceSelf_block_copy_0, __YTObject__testReferenceSelf_block_dispose_0};
static void _I_YTObject_testReferenceSelf(YTObject * self, SEL _cmd) {
__attribute__((objc_ownership(weak))) typeof(self) weakself = self;
__YTObject__testReferenceSelf_block_impl_0 blockImpl =
__YTObject__testReferenceSelf_block_impl_0((void *)__YTObject__testReferenceSelf_block_func_0,
&__YTObject__testReferenceSelf_block_desc_0_DATA,
weakself,
570425344)
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, s
el_registerName("setBlk:"),
((void (*)())&blockImpl));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("blk"))();
}
int main(int argc, const char * argv[]) {
YTObject *obj = ((YTObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YTObject"), sel_registerName("new"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)obj, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_fk_19cr58zj0f7f19001k_mxzlm0000gp_T_main_6f39dd_mii_0);
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("testReferenceSelf"));
return 0;
}
從上面的代碼中可以看到__YTObject__testReferenceSelf_block_impl_0結(jié)構(gòu)體中weakself成員是一個__weak修飾的YTObject類型對象,也就是說__YTObject__testReferenceSelf_block_impl_0對YTObject的依賴是弱依賴。weak修飾變量是在runtime中進行處理的,在YTObject對象的Dealloc方法中會調(diào)用weak引用的處理方法,從weak_table中尋找弱引用的依賴對象,進行清除處理,可以查看Runtime源碼中objc_object::clearDeallocating該方法的處理邏輯,另外關(guān)于__weak修飾的變量的詳細(xì)處理可以查看Runtime相關(guān)的知識
Block弱引用對象轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖:

關(guān)于具體的weakSelf和strongSelf可以參考這篇文章深入研究Block用weakSelf、strongSelf、@weakify、@strongify解決循環(huán)引用中的描述
weakSelf 是為了block不持有self,避免Retain Circle循環(huán)引用。在 Block 內(nèi)如果需要訪問 self 的方法、變量,建議使用 weakSelf。
strongSelf的目的是因為一旦進入block執(zhí)行,假設(shè)不允許self在這個執(zhí)行過程中釋放,就需要加入strongSelf。block執(zhí)行完后這個strongSelf 會自動釋放,沒有不會存在循環(huán)引用問題。如果在 Block 內(nèi)需要多次 訪問 self,則需要使用 strongSelf。
結(jié)束
以上就是關(guān)于Block底層實現(xiàn)的一些分析,不妥之處敬請指教
參考
深入研究Block用weakSelf、strongSelf、@weakify、@strongify解決循環(huán)引用