通過窺探Block
的底層實現(xiàn)苦银,解答以下問題
1.Block
底層數(shù)據(jù)結構是什么,本質是什么
2.Block
與其所訪問的外部變量的關系
3.Block
的內(nèi)存管理
Block的本質是什么?是函數(shù)单绑?代碼塊嘱函?OC對象?
簡單起見列另,我們在main.m
文件中寫一個沒有任何參數(shù)訪問的簡單的Block
芽腾,通過分析其源碼窺探Block
的本質
定義一個簡單的Block
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
block = ^{
NSLog(@"this is block");
};
}
return 0;
}
我們知道OC是基于C/C++實現(xiàn)的,接下來我們通過命令行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名
將main.m
文件轉化成對應的.cpp
文件页衙,窺探Block
的本質摊滔。
第一步:在.cpp
文件中檢索int main(int argc, const char * argv[])
定位main
函數(shù)位置阴绢,從而找到我們寫的代碼
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock block;
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}
return 0;
}
為了方便閱讀我們將上述源碼中的核心部分((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
中的強制類型轉換去掉,可以得到&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)
艰躺,到此可以清楚的看到這里調用了一個函數(shù)__main_block_impl_0(...)
呻袭,傳入了兩個參數(shù)__main_block_func_0
和__main_block_desc_0_DATA
证舟。并將函數(shù)返回值的地址賦值給了block
拣展。接下來我們分別分析函數(shù)及這兩個參數(shù)。
首先我們看下函數(shù)__main_block_impl_0
在.cpp
文件中進一步檢索函數(shù)__main_block_impl_0
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;
}
};
通過上述源碼咽白,可以看到__main_block_impl_0(...)
是結構體struct __main_block_impl_0
的構造函數(shù)页响。也就是說Block
的本質就是結構體struct __main_block_impl_0
繼續(xù)檢索傳入的參數(shù)__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d9b536_mi_0);
}
可以看出這就是我們寫的NSLog(@"this is block");
即block的實現(xiàn)部分篓足。
繼續(xù)檢索__main_block_desc_0_DATA
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)};
分析上述源碼可知,這是一個存儲了結構體struct __main_block_impl_0
大小信息等描述信息的結構體闰蚕。
分析完入?yún)⒑笳煌希覀冊僖淮螌⒛抗饣氐浇Y構struct __main_block_impl_0
上
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;
}
};
進一步分析其成員變量,其中struct __main_block_desc_0
我們上邊已經(jīng)看過了没陡。接下來我們檢索成員變量struct __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
可與看到一個很熟悉的指針isa
指針涩哟,我們知道oc對象的基本特點就是有一個isa
指針,看到這你可能會猜想block
本身是否也是一個oc
對象呢盼玄?
答案是肯定的贴彼,block
本質的確是一個oc
對象,下面我們將通過其他方式驗證這一點埃儿。我們知道幾乎所有的oc
對象都繼承自NSObject
锻弓,嘗試打印Block
的父類。
NSLog(@"block class : %@", [block class]);
NSLog(@"block super class : %@", class_getSuperclass([block class]));
NSLog(@"block super super class : %@", class_getSuperclass(class_getSuperclass([block class])));
NSLog(@"block super super super class : %@", class_getSuperclass(class_getSuperclass(class_getSuperclass([block class]))));
// 輸出log
block class : __NSGlobalBlock__
block super class : __NSGlobalBlock
block super super class : NSBlock
block super super super class : NSObject
從上述log可以看出蝌箍,block
本身的確是一個oc
對象青灼,且其繼承自NSObject
Block對基礎數(shù)據(jù)類型的捕獲
定義三種基本類型變量,并在Block
中訪問他們
int globalInt = 1; // 全局變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
static int staticInt = 5; // 靜態(tài)變量
int autoInt = 10; // 自動變量(本質上是auto int autoInt = 10;省略了關鍵字auto妓盲,通常我們定義的都是這種變量)
block = ^{
NSLog(@"staticInt = %d", staticInt);
NSLog(@"autoInt = %d", autoInt);
NSLog(@"globalInt = %d", globalInt);
};
block();
}
return 0;
}
通過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名
得到對應的.cpp
文件杂拨。
在.cpp
文件中檢索int main(int argc, const char * argv[])
定位main
函數(shù)
int globalInt = 1;
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock block;
static int staticInt = 5;
int autoInt = 10;
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticInt, autoInt));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
觀察上述源碼,可以看到在構建block
的結構體struct __main_block_impl_0
時悯衬,比之前多傳了兩個參數(shù)&staticInt
和autoInt
弹沽,但是并沒有傳globalInt
,即:static變量傳了地址(地址傳遞)筋粗,局部auto變量傳了值(值傳遞)策橘,全局變量沒有傳遞。
接下來我們?nèi)?code>struct __main_block_impl_0中看下這些參數(shù)的處理
觀察上述代碼娜亿,可以看到結構體struct __main_block_impl_0
中多了兩個成員變量int *staticInt;
和int autoInt;
丽已,并且構造函數(shù)__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticInt, int _autoInt, int flags=0) : staticInt(_staticInt), autoInt(_autoInt)
將外邊傳過來的_staticInt
和_autoInt
分別賦值給了int *staticInt;
和int autoInt;
。也就是說Block分別對靜態(tài)變量和自動變量進行了地址捕獲和值捕獲
在看下__main_block_func_0
和__main_block_desc_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *staticInt = __cself->staticInt; // 取結構體中的int型指針變量staticInt給局部int型指針變量staticInt
int autoInt = __cself->autoInt; // 取結構體中int型變量autoInt給局部int型變量autoInt
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_0, (*staticInt)); // 打印局部int型指針staticInt指向值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_1, autoInt);//打印局部變量autoInt的值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_2, globalInt);//打印全局變量globalInt的值
}
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_desc_0
沒有變化
小結
- Block不會捕獲全局變量
- Block會對靜態(tài)變量進行地址捕獲
- Block會對自動變量進行值捕獲
那么Block為什么這么做呢买决,認真思考下就會發(fā)現(xiàn)這是一個很合乎情理的設計沛婴,對于全局變量由于它是全局的泻蚊,本身就可以在任意地方訪問,所以Block自然不需要捕獲它秒旋,就像函數(shù)中訪問全局變量不需要將其當參數(shù)傳進去一樣尘奏;對于局部靜態(tài)變量炫加,首先我們想象一種使用場景铺然,Block及局部靜態(tài)變量是在一個普通函數(shù)中的赋铝,并且Block作為函數(shù)返回值使用革骨,在這種情況下助隧,因為出了函數(shù)我們就無法訪問到局部變量了筑凫,所以首先Block肯定要對這個靜態(tài)局部變量進行捕獲巍实,再者我們知道靜態(tài)變量不會隨著大括號即函數(shù)的結束而銷毀膝昆,所以Block只需要對其進行地址捕獲即可挠唆。對于普通的自動局部變量谒麦,由于其會被銷毀,所以需要對其進行值捕獲
Block對OC對象的捕獲
為了演示耻蛇,我們先創(chuàng)建一個類LJPerson
@interface LJPerson : NSObject
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int age;
@end
和基礎變量一樣,我們也創(chuàng)建三種不同的LJPerson變量刁赦,并在Block中訪問他們
typedef void(^MyBlock)(void);
LJPerson *globalPerson; // 全局變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
globalPerson = [[LJPerson alloc] init];
globalPerson.name = @"globalPerson";
globalPerson.age = 1;
static LJPerson *staticPerson; // 靜態(tài)變量
staticPerson = [[LJPerson alloc] init];
staticPerson.name = @"staticPerson";
staticPerson.age = 10;
LJPerson *autoPerson = [[LJPerson alloc] init]; // 自動變量
autoPerson.name = @"autoPerson";
autoPerson.age = 20;
block = ^{
NSLog(@"globalPerson = %@", globalPerson.name);
NSLog(@"staticPerson = %@", staticPerson.name);
NSLog(@"autoPerson = %@", autoPerson.name);
};
block();
}
return 0;
}
同樣的生成對應的.cpp
文件,并檢索main函數(shù)int main(int argc, const char * argv[])
和結構體struct __main_block_impl_0
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock block;
globalPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_0);
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setAge:"), 1);
static LJPerson *staticPerson;
staticPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_1);
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setAge:"), 10);
LJPerson *autoPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_2);
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setAge:"), 20);
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticPerson, autoPerson, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
LJPerson **staticPerson; // 地址捕獲
LJPerson *autoPerson; // 值捕獲
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LJPerson **_staticPerson, LJPerson *_autoPerson, int flags=0) : staticPerson(_staticPerson), autoPerson(_autoPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
觀察上述源碼波闹,可知Block對OC變量的捕獲機制和基礎數(shù)據(jù)類型一樣酝豪。即:Block對變量的捕獲方式只取決于其是全局變量、靜態(tài)變量精堕、還是自動變量孵淘。
那么Block對OC類型變量的處理與基礎數(shù)據(jù)類型變量的處理究竟有什么不同呢?
檢索Block的成員變量struct __main_block_desc_0
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};
對比之前的源碼歹篓,可以發(fā)現(xiàn)這里多了兩個函數(shù)copy
瘫证,dispose
揉阎。
觀察結構體對象__main_block_desc_0_DATA
的入?yún)ⅲ芍?code>copy背捌、dispose
函數(shù)分別對應參數(shù)__main_block_copy_0
毙籽、__main_block_dispose_0
,檢索函數(shù)__main_block_copy_0
毡庆、__main_block_dispose_0
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->staticPerson, (void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->autoPerson, (void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
觀察函數(shù)兩個函數(shù)坑赡,可以看到__main_block_copy_0
內(nèi)部分別對staticPerson
、autoPerson
對象各調用了一次_Block_object_assign
方法么抗,__main_block_dispose_0
內(nèi)部分別對staticPerson
毅否、autoPerson
各調用了一次_Block_object_dispose
方法。也就是說Block除了捕獲OC對象的外蝇刀,還會對OC對象做內(nèi)存管理螟加。
小尾巴
到此我們已經(jīng)解決了開篇中的第1、2個問題吞琐,并且知道了Block
會對OC對象做內(nèi)存管理捆探,那么在后續(xù)的文章里我們將繼續(xù)探索Block
是如何進行內(nèi)存管理的以及當我們用__weak
、__block
修飾變量時究竟是在做些什么