引言-“毒雞湯”
一個好的iOS開發(fā),block
必不可少戳葵,都會用就乓,但是block
的底層原理,我們確都是“淺嘗輒止”拱烁,滿足開發(fā)就好生蚁。人們總說“俗話說”,但是很多俗話說戏自,不都是前人一步一個坑結(jié)結(jié)實實的踩了邦投,才總結(jié)出來的一句話。那么俗話說“逆水行舟擅笔,不進則退”志衣,是否也可以用在開發(fā)上?我覺得可以猛们。薪水可以變念脯,但是時間不等人。技術(shù)深度(廣度)應(yīng)該是至少跑贏時間(可能大盤看多了弯淘,收益跑贏大盤這句話就常掛嘴邊)绿店。
希望每一位開發(fā)者,都應(yīng)該具備“危機意識”,在有限的時間假勿,學(xué)到更多的技術(shù)借嗽。
話不多說,今天這個文章转培,是探索block
的底層原理恶导。
block 底層結(jié)構(gòu)
【1】普通block
按照我們探索類的內(nèi)存時堡距,用到的探索方式兆蕉,首先查看編譯成底層c++的代碼是怎么樣的虎韵。 在main.m
中設(shè)計代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 1001;
NSString *str = @"百事可樂";
void (^qlBlock)(void) = ^{
NSLog(@"a = %d -- str = %@",a,str);
};
qlBlock();
}
return 0;
}
打開終端,通過xcrun
指令將main.m
轉(zhuǎn)成main.cpp
文件:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
打開main.cpp
驶社,拉到最底下亡电,找到main函數(shù)
硅瞧。將括號內(nèi)的類型轉(zhuǎn)換刪除,得到精簡代碼如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 1001;
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_wv_3h_d7hqj7zz300r8bmzy6_y00000gn_T_main_2abb7f_mi_0;
// __main_block_impl_0函數(shù)
void (*qlBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a, str, 570425344);
qlBlock->FuncPtr(qlBlock);
}
return 0;
}
1或辖、void (*qlBlock)(void)
賦值為一個函數(shù)調(diào)用__main_block_impl_0 ()
;
2枣接、搜索__main_block_impl_0
找到block的底層
實際上也是一個結(jié)構(gòu)體颂暇,并且__main_block_impl_0 ()
是block的構(gòu)造函數(shù)
,源碼如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 將block外部的變量a捕獲成為自己的成員變量
NSString *str; // 將block外部的變量str捕獲成為自己的成員變量
// 構(gòu)造函數(shù):
// 參數(shù)1但惶、void *fp:傳的是函數(shù)__main_block_func_0()耳鸯,該函數(shù)就是block的執(zhí)行代碼塊 ^{ },這個fp傳給了 FuncPtr膀曾,在qlBlock->FuncPtr(qlBlock);中調(diào)用
// 參數(shù)2片拍、struct __main_block_desc_0 *desc:block的信息結(jié)構(gòu)體
// 參數(shù)3、4 外部捕獲的變量妓肢,a(_a), str(_str) 分別表示_a賦值給a捌省,_str賦值給str
//
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSString *_str, int flags=0) : a(_a), str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3、qlBlock->FuncPtr(qlBlock);
調(diào)起block碉钠,FuncPtr
已經(jīng)在前面的構(gòu)造函數(shù)中賦值為__main_block_func_0 ()函數(shù)
纲缓。因此卷拘,此處調(diào)的就是這個函數(shù),并把block自己傳進去祝高。該block已捕獲了外部變量a栗弟,str
。
【2】外部變量加上 __block
關(guān)鍵字
我們將外部變量a和str分別加上__block
關(guān)鍵字工闺,并在block執(zhí)行塊中修改a和str乍赫,main.m代碼設(shè)計如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 1001;
__block NSString *str = @"百事可樂";
void (^qlBlock)(void) = ^{
a = 2002;
str = @"可口可樂";
NSLog(@"a = %d -- str = %@",a,str);
};
qlBlock();
}
return 0;
}
打開終端,通過xcrun
指令將main.m
轉(zhuǎn)成main.cpp
文件:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
打開main.cpp
改鲫,拉到最底下,找到main函數(shù)
缕题。將括號內(nèi)的類型轉(zhuǎn)換刪除,得到精簡代碼如下:
1瓶摆、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) { // 將 _a-> __forwarding賦值給a
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
2、main函數(shù)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__Block_byref_a_0 a = {0,&a, 0, sizeof(__Block_byref_a_0), 1001};
// __main_block_impl_0()
void (*qlBlock)(void) = (__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344));
// 調(diào)用
qlBlock->FuncPtr)(qlBlock);
}
return 0;
}
3、生成的a結(jié)構(gòu)體
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
4荐吉、block執(zhí)行的代碼塊
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {// 傳入block自己
// 獲取block生成的并賦值好的a
__Block_byref_a_0 *a = __cself->a; // bound by ref
// 結(jié)構(gòu)體a通過__forwarding獲取結(jié)構(gòu)體a內(nèi)部的成員變量a,并進行修改
(a->__forwarding->a) = 2002;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_wv_3h_d7hqj7zz300r8bmzy6_y00000gn_T_main_6d01e1_mi_0,(a->__forwarding->a));
}
由編譯后的源碼可知悦穿,
1、外部變量加了__block
后瞬沦,block對外部變量的捕獲,不再是單純的生成與之對應(yīng)的成員變量绣的,而是在內(nèi)部生成__Block_byref_a_0 *a
的結(jié)構(gòu)體指針變量。
2、在main函數(shù)
中踢故,先將外部的__block int a
編譯后,生成一個結(jié)構(gòu)體__Block_byref_a_0
(其內(nèi)部如上3淋纲、生成的a結(jié)構(gòu)體
,該結(jié)構(gòu)體內(nèi)部生成一個對應(yīng)的int a伙窃,用于接收外部int a的初始值1001,同時將外部int a的地址&a
賦值給__forwarding
)鳍怨,初始化賦值代碼:__Block_byref_a_0 a = {0,&a, 0, sizeof(__Block_byref_a_0), 1001};
3窿冯、到此為止醒串,生成的結(jié)構(gòu)體a的__forwarding
為外部int a的地址,
block結(jié)構(gòu)體內(nèi)部的*a
與外部變量a
所指向的內(nèi)存空間是同一個缠沈,捕獲的外部變量會隨著block生成的對應(yīng)成員變量改變而改變。
block 底層結(jié)構(gòu)小結(jié)
1柬赐、block的本質(zhì)是一個結(jié)構(gòu)體;
2酝陈、block的定義沉帮,是通過block的結(jié)構(gòu)體內(nèi)部的構(gòu)造函數(shù),對block內(nèi)部的impl、desc進行賦值茄蚯。
3渗常、結(jié)構(gòu)體impl內(nèi)部有4個成員變量:isa、Flags、Reserved癌椿、FuncPtr
缩功,在block構(gòu)造函數(shù)賦值時,將isa賦值為默認的NSConcreteStackBlock
地址(此為編譯階段势木,運行時這個isa會賦值成真實類型)胰蝠,F(xiàn)uncPtr 的賦值是定義的block執(zhí)行代碼塊躲庄。
4噪窘、block捕獲外部變量,是在block內(nèi)部生成對應(yīng)的成員變量浩习,并通過構(gòu)造函數(shù)將捕獲的變量賦值給內(nèi)部生成的成員變量--(值拷貝)
洽蛀。
5、block捕獲的外部變量驮审,如果用__block
修飾,則不再是將值傳給block內(nèi)部生成的變量峡竣,而是將外部變量的地址傳給內(nèi)部成員變量荠列,達到你動我動的效果
--(指針拷貝)
费就。
block 源碼
1固额、在蘋果官網(wǎng)Source Browser
下載libclosure-79逝慧,解壓打開Blocks
工程。
2沈堡、在原來的objc工程
的main.m
中窿给,定一個最簡單的block,并打上斷點角撞,運行到斷點后沛申,打開匯編(Debug--Debug Workflow -- Always Show Disassembly
)。
3、在匯編中趁桃,查看block的類型贴捡,以及將要
call
的符號4、點擊
step into
,進入到objc_retainBlock
函數(shù)中,可看到將要訪問_Block_copy
蝶念,此函數(shù)即可將 stack block 復(fù)制成 malloc block
5、繼續(xù)點擊
step into
會回到main的匯編廷蓉。因此_Block_copy
是我們繼續(xù)探索的函數(shù)符號全封。打開1的Blocks工程。全局搜索_Block_copy
桃犬,源碼如下(注意看注釋):
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
// block的真實結(jié)構(gòu)Block_layout
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
// 如果是釋放狀態(tài)刹悴,沒必要進行下一步處理,直接返回aBlock
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
// 如果是global類型的block攒暇,直接返回恒削。
return aBlock;
}
else {
// 如果不是global block,那么只有兩種情況:1肥橙、棧block(StackBlock)搏色,2、堆block(MallocBlock)
// 在編譯階段当娱,暫時會將block標記成棧block散休,是因為在編譯階段胁勺,若是對block進行malloc開辟內(nèi)存的話络拌,會增加編譯器的壓力
// 來到運行時(runtime)崭倘,block捕獲了外部變量坞淮,則需要將棧block 的大小付枫,malloc(size)開辟一個內(nèi)存空間
// Its a stack block. Make a copy.
size_t size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);
if (!result) return NULL;
// 開始copy
memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
// invoke的copy
result->invoke = aBlock->invoke;
#if __has_feature(ptrauth_signed_block_descriptors)
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
uintptr_t oldDesc = ptrauth_blend_discriminator(
&aBlock->descriptor,
_Block_descriptor_ptrauth_discriminator);
uintptr_t newDesc = ptrauth_blend_discriminator(
&result->descriptor,
_Block_descriptor_ptrauth_discriminator);
result->descriptor =
ptrauth_auth_and_resign(aBlock->descriptor,
ptrauth_key_asda, oldDesc,
ptrauth_key_asda, newDesc);
}
#endif
#endif
// reset refcount
// 重置refcount和配置flags
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.
// 將block類型標記為堆block(MallocBlock)
result->isa = _NSConcreteMallocBlock;
return result;
}
}
6秤涩、通過源碼可知流程如下:
a)oc定義的block 不 捕獲外部變量
---->編譯器
--->global block
---->運行時
--->return global block
b)oc定義的block捕獲外部變量
---->編譯器
--->stack block
---->運行時_Block_copy
--->malloc block
7后频、block的底層結(jié)構(gòu)為struct Block_layout
結(jié)構(gòu)體臊泰,源碼如下(注意注釋):
struct Block_layout {
// isa的指向:也可以說是block的類型:1、global block越走;2、stack block拧粪;3扁达、malloc block
void * __ptrauth_objc_isa_pointer isa;
// 標識
volatile int32_t flags; // contains ref count
int32_t reserved;
// 執(zhí)行函數(shù)
BlockInvokeFunction invoke;
// block的信息描述
struct Block_descriptor_1 *descriptor;
// imported variables
};
- 7.1但绕、結(jié)構(gòu)體
Block_layout
的成員變量struct Block_description_1
開始选脊,往下都是為可選參數(shù)
,也就是說脸甘,block的描述信息恳啥,可以在以下結(jié)構(gòu)體中選擇(但是具體如何設(shè)置,根據(jù)#define條件來配置丹诀,在哪兒配置钝的,我們不得而知翁垂,然我們可以通過反推法
來了解,即不知道setter
硝桩,通過getter
來了解):
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_descriptor_small {
uint32_t size;
int32_t signature;
int32_t layout;
/* copy & dispose are optional, only access them if
Block_layout->flags & BLOCK_HAS_COPY_DIPOSE */
int32_t copy;
int32_t dispose;
};
- 7.2 block desc的幾種情況
- 7.2.1
Block_descriptor_1
:搜索_Block_get_descriptor
沿猜,源碼如下:
// getter 函數(shù)
static inline void *_Block_get_descriptor(struct Block_layout *aBlock)
{
void *descriptor;
#if __has_feature(ptrauth_signed_block_descriptors)
if (!(aBlock->flags & BLOCK_SMALL_DESCRIPTOR)) {
descriptor =
(void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
} else {
uintptr_t disc = ptrauth_blend_discriminator(
&aBlock->descriptor, _Block_descriptor_ptrauth_discriminator);
descriptor = (void *)ptrauth_auth_data(
aBlock->descriptor, ptrauth_key_asda, disc);
}
#elif __has_feature(ptrauth_calls)
descriptor = (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
#else
descriptor = (void *)aBlock->descriptor;
#endif
return descriptor;
}
// setter函數(shù)
static inline void _Block_set_descriptor(struct Block_layout *aBlock, void *desc)
{
aBlock->descriptor = (struct Block_descriptor_1 *)desc;
}
- 7.2.2
Block_descriptor_2
:全局搜索Block_descriptor_2
,源碼如下(看下圖解釋):
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1);// 看下圖解釋
return (struct Block_descriptor_2 *)desc;
}
- 7.2.3
Block_descriptor_3
:全局搜索Block_descriptor_3
碗脊,源碼如下(看上圖解釋):
若Block_descriptor_2存在
啼肩,則指針偏移在desc的基礎(chǔ)上,加上Block_descriptor_1的大小望薄,再加上Block_descriptor_2的大小疟游,即可得到Block_descriptor_3。(desc3 = desc + sizeOf(desc1) + sizeOf(desc2))
若Block_descriptor_2不存在
痕支,則指針偏移在desc的基礎(chǔ)上,加上Block_descriptor_1的大小蛮原,即可得到Block_descriptor_3卧须。(desc3 = desc + sizeOf(desc1) )
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
block 調(diào)試
1、未捕獲外部變量:回到objc工程儒陨,在main.m
添加如下代碼花嘶,并添加斷點:
2、捕獲外部變量:
3蹦漠、添加
_Block_copy符號斷點
椭员,運行時,先暫時關(guān)閉_Block_copy
斷點笛园,因為在運行時會有一些系統(tǒng)的block會調(diào)用此函數(shù)進行復(fù)制隘击,我們只需要在自定義的block斷點停住時,把_Block_copy
斷點激活即可研铆。打開Debug 匯編
埋同。3.1:我們通過
lldb調(diào)試
的指令register read
讀取寄存器的情況(真機的寄存器名字為x0,x1等),最終看到在_Block_copy函數(shù)
內(nèi)部的匯編block的類型的變化棵红,對應(yīng)上面的_Block_copy源碼
即可凶赁。3.2:繼續(xù)點擊
step over
,一直回到main.m
時逆甜,在block調(diào)用處打上斷點虱肄。通過lldb調(diào)試
的指令register read
讀取寄存器的情況,如圖所示:若外部變量為對象交煞,則會多一個
copy和dispose
豫喧。即7.1中的block信息描述選擇穷当。
<__NSStackBlock__: 0x7ffeefbff428>
signature: "v8@?0"
invoke : 0x1000038e0 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__main_block_invoke)
copy : 0x100003910 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__copy_helper_block_e8_32s)
dispose : 0x100003950 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__destroy_helper_block_e8_32s)
3.3:signature的值可參考前面的文章類的方法底層,是一樣的。具體例子:lldb調(diào)試:[NSMethodSignature signatureWithObjCTypes:"v8@?0"];
打印結(jié)果如下(注意注釋):
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"];
<NSMethodSignature: 0x7afa2cf5db0b894b>
number of arguments = 1
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?' // 證明了block的encoding類型是`@?`
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
block 捕獲
【1】無__block
修飾
- 1.1 在
main.m
中設(shè)計代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [NSObject alloc];
void (^ qlBlock)(void) = ^{
NSLog(@"---%@---",obj);
};
qlBlock();
}
return 0;
}
- 1.2 通過
xcrun
將源碼編譯成c/c++。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
將代碼進行排版岛抄,清除一些類型強轉(zhuǎn),簡化后的代碼如下:
- 1.3 從入口函數(shù)main()中可以看到,
^{};
最終翻譯成函數(shù)__main_block_impl_0()
,該函數(shù)的入?yún)⒎治觯?/li> - a)
__main_block_func_0
是一個函數(shù)指針平斩,是block回調(diào)執(zhí)行的代碼塊 - b)(重點)
&__main_block_desc_0_DATA
是取結(jié)構(gòu)體__main_block_desc_0
的地址,該結(jié)構(gòu)體賦值情況咽块,即:
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
具體對應(yīng)的變量為:
size_t reserved = 0;
size_t Block_size = sizeof(struct __main_block_impl_0);
void (copy)(struct __main_block_impl_0, struct __main_block_impl_0) = __main_block_copy_0;
void (dispose)(struct __main_block_impl_0*) = __main_block_dispose_0;
- c)
obj
即我們定義的obj變量绘面,obj作為參數(shù)傳入block內(nèi)部生成的obj變量,并將外部obj賦值給內(nèi)部obj侈沪。由于obj是一個NSObject *
的指針揭璃,此處obj傳入相當于block生成的obj與外部的obj指向同一片堆空間。(block捕獲外部變量的小結(jié)在上面的block 底層結(jié)構(gòu)小結(jié)
) - d)
570425344
數(shù)字參數(shù)亭罪,是一個暫時用不到的參數(shù)瘦馍。暫不考慮。
【2】有__block
修飾
我們在NSObject *obj前加入__block
应役,然后 按照第【1】步的步驟情组,將main.m
編譯成c/c++。
將代碼進行排版箩祥,清除一些類型強轉(zhuǎn)院崇,簡化后的代碼如下:
- 2.1 與前面的
無 __block 修飾
的外部變量捕獲相比,加了__block
后袍祖,外部變量NSObject *obj
邏輯編譯成了一個結(jié)構(gòu)體__Block_byref_obj_0 obj
底瓣,該結(jié)構(gòu)體源碼如下: - 2.2 用
__block
修飾的外部遍歷,block構(gòu)造函數(shù)__main_block_impl_0
傳入的是&obj
蕉陋,也就是obj本身的地址
捐凭,也就是說,外部的obj隨著block內(nèi)部的obj的變化而變化寺滚。 - 2.3
無__block
修飾的外部變量柑营,block構(gòu)造函數(shù)__main_block_impl_0
傳入的是obj
,也就是obj指針指向的堆地址
村视。
【3】_Block_object_assign
- 【3.1】 通過上面將block編譯成c/c++文件的分析官套。我們可以看到在封裝
block信息描述的結(jié)構(gòu)體desc
中,傳入了一__main_block_copy_0
函數(shù)和一個__main_block_dispose_0
函數(shù)蚁孔。兩個函數(shù)的實現(xiàn)如下:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign(&dst->obj, src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose(src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}
_Block_object_assign()
傳入了三個參數(shù):1奶赔、&dst->obj
:目標block的obj地址;2杠氢、src->obj
:原block的obj變量站刑;3、8
:BLOCK_FIELD_IS_BYREF鼻百,表示該對象用__block 修飾
了绞旅。
The flags parameter of _Block_object_assign and _Block_object_dispose is set to
* BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
* BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
即:
- 捕獲的外部變量為
普通oc對象
摆尝,則傳入3;- 捕獲的外部變量為
其他block
因悲,則傳入7堕汞;- 捕獲的外部變量為
用__block修飾的變量
,則傳入8晃琳;
- 【3.2】 打開
Blocks.xcodeproj
工程讯检,全局搜索_Block_object_assign
,找到源碼如下:
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:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*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:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
分析:
通過flags & BLOCK_ALL_COPY_DISPOSE_FLAGS
得到捕獲的變量是什么類型卫旱,通過switch分支進行分類處理人灼。
- 3.2.1
case BLOCK_FIELD_IS_OBJECT:
如果捕獲的變量是oc object ,則將捕獲的object進行copy顾翼。(通過_Block_retain_object()
回調(diào)給_Block_retain_object_default()
投放,并將原block賦值給目標block*dest = object;
) - 3.2.2
case BLOCK_FIELD_IS_BLOCK:
如果捕獲的對象是一個block,則走_Block_copy()
函數(shù)暴构,并將結(jié)果賦值給目標block跪呈。 - 3.2.3(后面重點分析)
case BLOCK_FIELD_IS_BYREF :
如果捕獲的變量是用__block
修飾的或者__weak __block
修飾的,將原block通過_Block_byref_copy()
賦值給目標block取逾。 - 3.2.4
其余情況均是將原block直接賦值給目標block
:*dest = object;
【4】_Block_byref_copy
全局搜索_Block_byref_copy
,得到源碼如下:
static struct Block_byref *_Block_byref_copy(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) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 【4.1】判斷語句
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0)
苹支,其中BLOCK_REFCOUNT_MASK
為引用計數(shù)
相關(guān)的掩碼砾隅。如果此處判斷 == 0,說明引用計數(shù)為0
债蜜,并沒有將block copy到堆上晴埂。反之(src->forwarding->flags & BLOCK_REFCOUNT_MASK) != 0
,則說明已經(jīng)將原block copy到堆上寻定。接著判斷flags
是否需要釋放儒洛,需要釋放則進行flags的重新賦值
。
-【4.2】未copy到堆上狼速,流程如圖所示:
-【4.3】判斷語句if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE)
如果成立琅锻,說明已經(jīng)捕獲到了外部變量,具體如圖所示:
【5】byref_keep
由上圖我們可知byref_keep()
是為了保存(毕蚝活)外部捕獲變量的生命周期恼蓬,如果不進行keep
,則有可能會出現(xiàn)src為空了之后僵芹,開辟的空間的內(nèi)容也被清空了处硬。
byref_keep
的源碼為:
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
返回到main_byref.cpp
文件中,找到struct __Block_byref_obj_0{}
結(jié)構(gòu)體拇派,內(nèi)部的copy
和dispose
函數(shù)分別賦值給了byref_keep
和byref_destroy
荷辕。也就是說在編譯后的cpp文件中凿跳,在main函數(shù)
里,__block修飾的NSObject *obj
疮方,編譯所生成的結(jié)構(gòu)體的賦值控嗜,__Block_byref_id_object_copy_131
賦值給了結(jié)構(gòu)體__Block_byref_obj_0
的copy
函數(shù);__Block_byref_id_object_dispose_131
賦值給了__Block_byref_obj_0
的dispose
函數(shù)案站。
也就是說:
byref_keep = __Block_byref_id_object_copy_131函數(shù)
byref_destroy = __Block_byref_id_object_dispose_131函數(shù)
由此可知躬审,byref_keep
函數(shù)的調(diào)用,是調(diào)用__Block_byref_obj_0
的copy
函數(shù)蟆盐。也就是把_Block_object_assign
調(diào)用一遍承边,即
// (*src2->byref_keep)(copy, src);
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign(dst + 40, * (src + 40), 131);
}
- a)
dst
:即byref_keep
函數(shù)的第1個參數(shù)copy
:新開辟的block(目標block)。 - b)
src
:即byref_keep
函數(shù)的第2個參數(shù)src
:原來的block石挂。 - c)
dst+40
博助、src + 40
,是在目標block
的首地址平移40字節(jié)痹愚,對應(yīng)結(jié)構(gòu)體__Block_byref_obj_0
內(nèi)存結(jié)構(gòu)富岳,平移40字節(jié),得到的是NSObject *obj
拯腮,計算方式如下:
struct __Block_byref_obj_0 {
void *__isa; // 8
__Block_byref_obj_0 *__forwarding;// 8
int __flags; // 4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*);// 8
void (*__Block_byref_id_object_dispose)(void*);// 8
NSObject *obj;
};
則_Block_object_assign(dst + 40, * (src + 40), 131);
變成為_Block_object_assign(obj, * obj, 131);
窖式。由上面_Block_object_assign
函數(shù)的源碼可知,此刻的switch分支动壤,走的就是BLOCK_FIELD_IS_OBJECT
分支萝喘。
因此,block捕獲__block修飾
的外部變量琼懊,
總結(jié)
- block的本質(zhì)是一個
結(jié)構(gòu)體
阁簸,并由結(jié)構(gòu)體的構(gòu)造函數(shù)初始化和賦值
。 - block的類型有
3種
哼丈,global block
启妹,stack block
,malloc block
醉旦。 - block
不捕獲外部變量
則為global block
饶米。 - block
捕獲外部變量
則編譯時為stack block
,運行時_Block_copy
后變成malloc block
髓抑。 - 結(jié)構(gòu)體impl內(nèi)部有4個成員變量:isa咙崎、Flags、Reserved吨拍、FuncPtr褪猛,在block構(gòu)造函數(shù)賦值時,將isa賦值為默認的NSConcreteStackBlock地址(此為編譯階段羹饰,運行時這個isa會賦值成真實類型)伊滋,F(xiàn)uncPtr 的賦值是定義的block執(zhí)行代碼塊碳却。
- block結(jié)構(gòu)體中的
Block_descriptor_1
和Block_byref
一樣內(nèi)存是連續(xù)的
可通過指針平移來獲取對應(yīng)位置的值。
Block_descriptor_1
---Block_descriptor_2
---Block_descriptor_3
內(nèi)存是連續(xù)的笑旺,
Block_byref
---Block_byref_2
---Block_byref_3
內(nèi)存是連續(xù)的 - block捕獲外部變量昼浦,是在block內(nèi)部生成對應(yīng)的成員變量,并通過構(gòu)造函數(shù)將捕獲的變量賦值給內(nèi)部生成的成員變量 --
(值拷貝)
筒主。 - 捕獲用
__block修飾
的外部變量(指針拷貝)3層copy
:
(a)將block從棧
copy到堆
上
(b)接著block捕獲Block_byref
結(jié)構(gòu)體关噪,并對其進行copy
(c)Block_byref
對內(nèi)部的變量(本文為NSObject *obj)進行copy。