Objc Block實現分析
Block在iOS開發中使用的頻率是很高的,使用的場景包括接口異步數據的回調(AFN)、UI事件的回調(BlockKits)、鏈式調用(Masonry)、容器數據的遍歷回調(NSArray、NSDictionary),具體的用法以及使用Block的一些坑這里就不一一贅述了,本文會從源代碼的層面分析我們常用的Block的底層實現原理,做到知其然知其所以然。
本文會從如下幾個主題切入,層層遞進分析Block的底層原理,還原Block本來的面目
- 無參數無返回值,不使用變量的Block分析
- 使用自動變量的Block分析
- 使用__block修飾的變量的Block分析
- Block引用分析
無參數無返回值,不使用變量的Block分析
有如下的源代碼,創建一個簡單的block,在block做的處理是打印一個字符串,然后執行這個block。接下來會從源碼入手對此進行分析:block是如何執行的
// 無參數無返回值的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定義和調用的代碼變成了大幾十行,不過該源代碼還算是相對簡單的,我們從main函數入口逐步的進行分析:
main
函數中創建__main_block_impl_0
類型的實例
__main_block_impl_0
結構體是什么鬼呢?我們看__main_block_impl_0
結構體的實現,包含了__block_impl
結構體和__main_block_desc_0
結構體指針,為了方便,現在頁先不用管__main_block_desc_0
結構體,目前他還沒有真正的使用到,后面講到的__block
修飾自動變量以及Block對對象的引用關系,設計到內存的拷貝和釋放的時候會使用到該變量。__block_impl
結構體包含了4個成員,為了簡單分析,我們只關注其中的FuncPtr
成員的FuncPtr
成員,這個也是最重要的成員,其它的先忽略不計。
__main_block_impl_0
實例的初始化參數
第一個是__main_block_func_0
函數的地址,__main_block_func_0
函數是什么呢,其實就是Block執行的方法,可以看到里面的實現就是一個簡單的打印而已,和我們定義在block中的實現一樣的,該參數用于初始化impl
成員的。至于第二個參數先不管,在這個例子木有用到。
__main_block_impl_0
實例的調用
創建了__main_block_impl_0
結構體類型的實例之后,接下來就是獲取到里面的FuncPtr
指針指向的方法進行調用,參數是__main_block_impl_0
結構體類型的實例本身,上一步創建步驟可知,FuncPtr
指針是一個函數指針指向__main_block_func_0
函數的地址,所以這里本質上就是__main_block_func_0
函數的調用,結構是打印字符串"block invoke",一個簡單的block執行孫然在代碼量上增加了,其實也不算復雜。
注意點:
(__block_impl *)blk
這里做了一個強制轉換,blk是__main_block_impl_0
類型的實例的指針,根據結構體的內存布局規則,該結構體的第一個成員為__block_impl
類型,所以可以強制轉換為__block_impl *
類型。
由上面的分析,可以得出如下的結論:使用變量的Block調用本質上是使用函數指針調用函數
// 使用`clang -rewrite-objc main.m`命令重寫為C++語法的源文件,對結果分析如下
// __block_impl結構體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// `__main_block_impl_0`是block的結構體,包含了兩個成員__block_impl類型的impl成員以及__main_block_desc_0類型的Desc成員;一個構造方法__main_block_impl_0,構造方法中初始化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執行的方法
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)};
// 重寫后的入口函數main
int main(int argc, const char * argv[]) {
// 創建__main_block_impl_0類型的實例,名稱為blk,這里把改實例強制轉換為`void(funcPtr *)(void)`型的方法指針,不懂為何,因為在下一步會把該方法指針強制轉換為對應的結構體,也就是說`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`類型的實例的指針,根絕結構體的內存布局規則,該結構體的第一個成員為`__block_impl` 類型,可以強制轉換為`__block_impl *`類型,獲取FuncPtr函數指針,強制轉換為`(void (*)(__block_impl *))`類型的函數指針,然后執行這個函數指針對應的函數,參數為blk
// 由上面的步驟可知,Block的調用本質上是使用函數指針調用函數
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
使用自動變量的Block分析
有如下的源代碼,創建一個簡單的block,在block做的處理是打印一個字符串,使用到外部的自動變量,然后執行這個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++語法的源文件。對比上一個的數據結構方法和調用步驟大體上是一樣的,只針對變化的地方進行分析
__main_block_impl_0
結構體的變化
增加了兩個成員:int類型的age成員、char*類型的name成員,用于保存外部變量的值,在初始化的時候就會使用自動變量的值初始化這兩個成員的值
調用的變化
__main_block_func_0
函數的參數struct __main_block_impl_0 *__cself
在這個例子有使用到了,因為兩個自動變量對應的值被保存在__main_block_impl_0
結構體中了,方法中有使用到這兩個變量,直接從__main_block_impl_0
結構體中獲取這兩個值,但是這兩個值是獨立于自動變量的存在了
由上面的分析,可以得出如下的結論:使用變量的Block調用本質上是使用函數指針調用函數,參數是保存在block的結構體中的,并且保存的值而不是引用
// 使用`clang -rewrite-objc main.m`命令重寫為C++語法的源文件,對結果分析如下
// __block_impl結構體
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成員;一個構造方法__main_block_impl_0,構造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個改結構體多了兩個成員,用于保存外部變量的值
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執行的方法
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)};
// 重寫后的入口函數main
// 由上面的步驟可知,使用變量的Block調用本質上是使用函數指針調用函數,參數是保存在block的結構體中的,并且保存的值而不是引用
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
的作用域中分別作了修改,從輸入的結果看看是兩次都生效了,接下來會從源碼入手對此進行分析:block
中是如何處理__block
修飾的自動變量,該自動變量的內存是如何變化的
// 源代碼
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++語法的源文件,對結果分析如下的注釋,該轉換后的代碼做了些許的調整,包括代碼的縮進和添加了結構體聲明,使得該代碼可以直接運行
// clang改寫后的代碼如下,以下代碼是經過調整可以直接運行的
struct __main_block_desc_0;
// __block_impl結構體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// `__block`修飾的變量對應的結構體,里面包含了該變量的原始值也就是age成員,另外還有一個奇怪的`__forwarding`成員,稍后我們會分析它的用處
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
// `__main_block_impl_0`結構體包含了四個成員`__block_impl`類型的impl成員、`__main_block_desc_0`類型的Desc成員、`__Block_byref_age_0 *`類型的age成員、char*類型的name成員;一個構造方法`__main_block_impl_0`,構造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個結構體的變化是age的類型變為了包裝自動變量的結構體了
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執行的方法
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拷貝函數
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銷毀函數
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};
// 重寫后的入口函數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修飾自動變量轉換為C++代碼的結構體關系圖:
該重寫的代碼大部分和上面分析過的例子代碼是類似,發現增加了兩個處理方法Block拷貝函數__main_block_copy_0
和Block銷毀函數__main_block_dispose_0
,這兩個函數會保存在__main_block_desc_0
結構體的copy
和dispose
成員中;另外添加了一個__Block_byref_age_0
結構體類型用戶處理__block
修飾的自動變量。以下針對這兩點從源代碼的角度進行一個分析
__main_block_copy_0
方法中調用到的_Block_object_assign
可以在 runtime.c 這里找到 ,主要看下_Block_object_assign
方法里面的處理邏輯
- flags參數值為8,是BLOCK_FIELD_IS_BYREF枚舉對應的值,會走到
_Block_byref_assign_copy
方法的調用步驟 -
_Block_byref_assign_copy
方法會在在堆上創建Block_byref
對象,也就是Block對象,并且把棧上和堆上的Block對象的forwarding
屬性值都修改為指向堆上的Block對象,這樣使用兩個對象的修改值都會修改為同一個地方
棧上的__block
自動變量__forwarding
指向關系以及拷貝到堆上之后__forwarding
指向關系如下圖所示
具體使用到的代碼和對應的注釋如下:
/*
* 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);
}
}
// 代碼會走到這個分支中,調用方法`_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調用)還是在堆上修改__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引用對象轉換為C++代碼的結構體關系圖:
從類圖上可以明顯的看到__YTObject__testReferenceSelf_block_impl_0
和YTObject
之間有循環依賴的關系,這樣NSLog(@"==dealloc==");
這段代碼最終是沒有調用到,也就是這里會出現Block循環引用導致內存泄漏問題
Block弱引用分析
Block的循環引用問題其中一種解決方案是可以使用weakself
來解除這種強引用關系,防止內存的泄漏,代碼的改造如下
@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++語言對應的代碼,重寫后的代碼如下
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
結構體中weakself
成員是一個__weak
修飾的YTObject
類型對象,也就是說__YTObject__testReferenceSelf_block_impl_0
對YTObject
的依賴是弱依賴。weak修飾變量是在runtime中進行處理的,在YTObject
對象的Dealloc方法中會調用weak引用的處理方法,從weak_table
中尋找弱引用的依賴對象,進行清除處理,可以查看Runtime源碼中objc_object::clearDeallocating
該方法的處理邏輯,另外關于__weak
修飾的變量的詳細處理可以查看Runtime相關的知識
Block弱引用對象轉換為C++代碼的結構體關系圖:
關于具體的weakSelf和strongSelf可以參考這篇文章深入研究Block用weakSelf、strongSelf、@weakify、@strongify解決循環引用中的描述
weakSelf 是為了block不持有self,避免Retain Circle循環引用。在 Block 內如果需要訪問 self 的方法、變量,建議使用 weakSelf。
strongSelf的目的是因為一旦進入block執行,假設不允許self在這個執行過程中釋放,就需要加入strongSelf。block執行完后這個strongSelf 會自動釋放,沒有不會存在循環引用問題。如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。
結束
以上就是關于Block底層實現的一些分析,不妥之處敬請指教