iOS Objective-C Block底層原理
在上一篇文章中我們對Block
做了簡單的介紹,下面我們通過這篇文章對Block
的底層原理進行探索犀被。
首先提出問題:
-
Block
的本質(zhì)是什么梳庆? -
Block
為什么需要調(diào)用block()
丛版? -
Block
是如何截獲外界變量的瓮下? -
__block
是如何實現(xiàn)的?
1. 通過Clang查看Block的底層實現(xiàn)
1.1 編譯后的代碼簡單分析
要想知道Block
的底層實現(xiàn),我們首先想到的就是通過Clang
編譯一下Block
代碼消请,然后看看其內(nèi)部的實現(xiàn)。我們創(chuàng)建一個block.c
的文件,內(nèi)部代碼如下:
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("hello block");
};
block();
return 0;
}
通過xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
命令叽奥,將.c
文件編譯成.cpp
文件,我們找到main
函數(shù)進行查看痛侍,編譯后的形式如下:
int main(){
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
去除掉類型強轉(zhuǎn)朝氓,可以將編譯后的代碼簡化成如下形式:
int main(){
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
block->FuncPtr(block);
return 0;
}
通過簡化后的代碼我們可以看出Block
等于__main_block_impl_0
函數(shù),該函數(shù)有兩個參數(shù)主届,其中第一個參數(shù)__main_block_func_0
就是我們在Block
代碼塊中寫的代碼赵哲。其編譯后的實現(xiàn)如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello block");
}
1.2 __main_block_impl_0
我們在編譯后的.cpp
文件內(nèi)搜索__main_block_impl_0
便可找起實現(xiàn),下面我們來看看其實現(xiàn):
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
是一個結構體君丁,在該結構體中第一個參數(shù)是一個__block_impl
類型的imp
枫夺,__block_impl
源碼如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
在__block_impl
內(nèi)有四個變量:
-
isa
: 類似于OC對象的isa,在這個例子中指向_NSConcreteStackBlock
绘闷,實際就是指向那種類型的Block
橡庞,相當于指向類 -
Flags
:這里是0 -
Reserved
: 保留字段 -
FuncPtr
:Block
代碼塊的函數(shù)指針较坛,通過該指針調(diào)用block
1.3 Block的調(diào)用
在編譯后的代碼中我們可以看出Block
的調(diào)用是通過block->FuncPtr(block)
來進行的。
- 可以看出
block
內(nèi)部聲明了一個__main_block_func_0
的函數(shù)扒最; - 在
__main_block_impl_0
中傳入的第一個參數(shù)就是__main_block_func_0
丑勤; - 在其內(nèi)部用
fp
表示,然后賦值給impl
的FuncPtr
屬性吧趣; - 所以我們可以可以通過
block->FuncPtr(block)
來進行調(diào)用Block
通過對Clang
編譯的源碼進行查看法竞,在block
內(nèi)部并不會自動調(diào)用,所以我們需要調(diào)用底層生成的函數(shù)__main_block_func_0
再菊,才能實現(xiàn)block
的調(diào)用
1.4 Block捕獲外界變量
1.4.1 僅使用變量
上面我們分析了一個最簡單的Block
爪喘,沒有任何的與外界交互,如果與外界交互時纠拔,我們的Block
又會是什么樣呢秉剑?
這里我們同樣使用Clang
去編譯一個可以捕獲外界變量的Block
,實現(xiàn)代碼如下:
#include "stdio.h"
int main(){
int a = 123;
void(^block)(void) = ^{
printf("hello block a = %d",a);
};
block();
return 0;
}
編譯后的結果:
int main(){
int a = 123;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("hello block a = %d",a);
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
通過以上編譯后的代碼我們可以看出稠诲,Block
如果想要捕獲外界變量侦鹏,就會在其內(nèi)部創(chuàng)建一個同名變量來存儲從外界捕獲的變量。并在__main_block_func_0
中取出捕獲的變量臀叙,以供函數(shù)調(diào)用的時候使用略水。
1.4.2 修改變量 (__block)
如果我們使用__block
修飾外界變量,并在Block
中修改了變量是什么樣子呢劝萤?
我們修改代碼為如下渊涝,然后通過Clang
去編譯:
#include "stdio.h"
int main(){
__block int a = 123;
void(^block)(void) = ^{
a = 10;
printf("hello block a = %d",a);
};
block();
return 0;
}
編譯后的結果:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
int main(){
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 123};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
通過以上編譯后的代碼我們可以看到,對于__block
修飾的變量在底層被編譯成了__Block_byref_a_0
類型的結構體:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
在這個結構體中我們可以通過一個叫做__forwarding
的成員變量來間接訪問我們定義的變量床嫌。
在此處生成的__main_block_impl_0
結構體中跨释,變量a
也是取的__Block_byref_a_0
類型的結構體指針。生成代碼如下:
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) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
對于__main_block_func_0
中的變量a
也同樣是取的a
的地址進行修改其中的值厌处。代碼如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 10;
printf("hello block a = %d",(a->__forwarding->a));
}
綜上所述對于使用__block
修飾的變量鳖谈,在通過Clang
編譯后又如下結論:
- 對于外界的變量會編譯為
__Block_byref_name_0
的結構體,其中name
是外界變量的名稱 - 結構體內(nèi)會保存外界變量的指針和值
- 通過結構體內(nèi)的
__forwarding
的成員變量來間接訪問我們定義的變量阔涉。 - 對于編譯后的
block
結構體__main_block_impl_0
內(nèi)部也會存儲一個外界變量的__Block_byref_name_0
類型的結構體指針 - 通過
block
結構體作為參數(shù)傳遞給生成的__main_block_func_0
對外界變量進行訪問缆娃。
所以此處并不是像2.1中的那樣只是創(chuàng)建了一個同名的變量那樣簡單,在這兩節(jié)中分別使用了值拷貝和指針拷貝兩種方法:
- 值拷貝:也就是淺拷貝瑰排,只拷貝數(shù)值贯要,且拷貝的值不可更改,指向不同的內(nèi)存空間
- 指針拷貝:也就是深拷貝椭住,生成的對象與原對象指向同一片內(nèi)存空間郭毕,在一處修改的時候另一處也會被修改
1.5 小結
通過上面的分析我們可以得出如下結論:
-
Block
在底層是一個結構體,同樣也可以使用%@
打印函荣,所以也可以理解為對象 -
Block
需要調(diào)用是因為Block
代碼塊在底層是一個函數(shù)显押,要想讓其執(zhí)行,所以需要調(diào)用 -
Block
捕獲外界變量時傻挂,會自動生成一個同名屬性 -
Block
捕獲并修改外界變量時乘碑,會生成一個__Block_byref_name_0
的結構體,并通過一個叫做__forwarding
的成員變量來間接訪問我們定義的變量 - 所以
__block
的原理是生成響應的結構體金拒,保存原始變量的指針和值兽肤,傳遞一個指針地址給Block
2. Block底層探索
2.1 查找Block的底層實現(xiàn)
通過以上對于Clang
編譯后Block
的探索后我們對Block
有了初步的了解,但是我們還是想知道Block
在底層的真正的實現(xiàn)绪抛,以及找一份開源代碼進行研究资铡,下面我們通過匯編去尋找一下Block
的底層實現(xiàn)和實現(xiàn)庫的位置。
我們創(chuàng)建一個iOS工程編寫一段Block
代碼幢码,并添加如下斷點笤休,然后開啟匯編調(diào)試Debug
->Debug Workflow
->Always Show Disassembly
運行程序后我們發(fā)現(xiàn)一個符號symbol
為objc_retainBlock
這里的匯編代碼是call
說明調(diào)用了這個符號,我們在這行匯編代碼處添加斷點症副,如下圖:
過掉原本的斷點店雅,來到上面這行處,然后按住command
鼠標點擊斷點處的向下的小箭頭來到如下圖所示的匯編代碼處:
通過上面的圖片我們可以知道此處又繼續(xù)調(diào)用了_Block_copy
贞铣,然后我們添加_Block_copy
符號斷點闹啦。過掉上面的斷點來到如下圖所示的匯編處:
通過上面這張圖片我們可以看到_Block_copy
實現(xiàn)于libsystem_blocks.dylib
源碼中。
我們可在Apple Opensource中下載各個版本的libclosure源碼辕坝。這里推薦一下LGCooci老師的libclosure-74-KCBuild窍奋,可以編譯運行的libclosure
,可以運行并斷點調(diào)試Block
底層的libclosure-74
源碼酱畅。
2.2 Block_layout
2.2.1 Block_layout源碼及分析
首先我們?nèi)炙阉?code>_Block_copy找到它的源碼如下:
_Block_copy源碼:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
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.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
從該函數(shù)的第一行代碼中我們看到了個Block_layout
琳袄,那么我們首先來看看Block_layout
這個結構體是什么,其實這就是我們block
底層的真正實現(xiàn)圣贸,源碼如下:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
-
isa
:指向block
類型的類挚歧,就是那幾種block
-
flags
:標識符,是一種位域結構吁峻,按位表示block
的一些信息 -
reserved
:保留字段 -
invoke
:函數(shù)指針滑负,指向具體的block
實現(xiàn)的調(diào)用地址 -
descriptor
:block
的附加信息(其實還有Block_descriptor_2
和Block_descriptor_3
)
2.2.2 flag 分析
在_Block_copy
函數(shù)中我們可以看到aBlock->flags & BLOCK_NEEDS_FREE
,說明flag
與BLOCK_NEEDS_FREE
相關用含,我們跳轉(zhuǎn)到BLOCK_NEEDS_FREE
找到如下枚舉代碼:
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位
BLOCK_DEALLOCATING
:釋放標記矮慕,一般常用BLOCK_NEEDS_FREE
做按位與操作,一同傳入 Flags啄骇,表示該block
是否可以釋放痴鳄。 - 第16位
BLOCK_REFCOUNT_MASK
:存儲引用計數(shù)的值,是一個可選用的參數(shù) - 第24位
BLOCK_NEEDS_FREE
:低16位是否有效的標志缸夹,程序根據(jù)它來決定是否增加或較少引用計數(shù)位的值 - 第25位
BLOCK_HAS_COPY_DISPOSE
:是否擁有拷貝輔助函數(shù)a copy helper function
- 第26位
BLOCK_HAS_CTOR
:是否擁有block
析構函數(shù) - 第27位
BLOCK_IS_GC
:標志是否有垃圾回收痪寻,應用于OS X
- 第28位
BLOCK_IS_GLOBAL
:標志是否是全局Block
- 第29位
BLOCK_USE_STRET
:與30位相反螺句,判斷當前Block
是否擁有一個簽名,用于runtime
時動態(tài)調(diào)用橡类。 - 第30位
BLOCK_HAS_SIGNATURE
:與29位相反蛇尚,判斷當前Block
是否擁有一個簽名,用于runtime
時動態(tài)調(diào)用顾画。 - 第31位:
BLOCK_HAS_EXTENDED_LAYOUT
:標志block
是否有擴展
2.2.3 descriptor 分析
descriptor
是block
的附加信息取劫,首先在``中看到的是Block_descriptor_1
,我們跳轉(zhuǎn)過去可以看到如下代碼:
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;// 保留信息
uintptr_t size;// block大小
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;//拷貝函數(shù)指針
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 依賴于block擴展布局
};
這里的Block_descriptor_1
是必選的Block_descriptor_2
和Block_descriptor_3
不是必選的:
Block_descriptor_2
需要flags
是:BLOCK_HAS_COPY_DISPOSE
才會存在研侣,Block_descriptor_3
需要flags
是BLOCK_HAS_SIGNATURE
和BLOCK_HAS_EXTENDED_LAYOUT
才會存在谱邪。
我們在Block_layout
中只看到Block_descriptor_1
那么是怎么訪問Block_descriptor_2
和Block_descriptor_3
的呢?我們可以在其構造方法中找到答案庶诡,就是經(jīng)過內(nèi)存平移訪問的惦银,源碼如下:
/****************************************************************************
Accessors for block descriptor fields
*****************************************************************************/
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
#endif
// Block 的描述 : copy 和 dispose 函數(shù)
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
// Block 的描述 : 簽名相關
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
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;
}
2.2.4 查看Block簽名
在面試中經(jīng)常會提到Block
簽名的問題,那么我們的Block
簽名到底是什么呢灌砖?在上一節(jié)中我們可以看到我們的Block
簽名存儲在Block_descriptor_3
中璧函,下面我們就定義一個block
通過讀取內(nèi)存的方式看一看Block
的簽名長什么樣。
Block 代碼:
void (^block)(void) = ^{
NSLog(@"test_block");
};
block();
以下這段話很重要;浴U合拧!撩幽!!
這里是緊密的根據(jù)上一節(jié)的內(nèi)容來的库继,因為block
在底層本質(zhì)是Block_layout
,其參數(shù)中isa
占8字節(jié)窜醉,flags
占4字節(jié)宪萄,reserved
占4字節(jié),invoke
占8字節(jié)榨惰,descriptor
占8字節(jié)拜英,所以我們讀取的第一個4段內(nèi)存中的第4個就是descriptor
的地址,根據(jù)上一節(jié)中我們的分析琅催,知道了Block_descriptor_3
的地址是由Block_descriptor_1
偏移進行讀取的居凶,根據(jù)_Block_descriptor_3
函數(shù)中的代碼,我們的Block
是否需要銷毀通過BLOCK_HAS_COPY_DISPOSE
進行判斷藤抡,在讀取的第一個四段內(nèi)存中的第二段0x50000000
與上flags
中的BLOCK_HAS_COPY_DISPOSE
也就是1 << 25
結果為0
侠碧,所以只需要偏移Block_descriptor_1
這個結構體的內(nèi)存大小,Block_descriptor_1
有兩個屬性缠黍,分別都是uintptr_t
類型弄兜,uintptr_t
實際就是long
占8字節(jié),兩個就是16字節(jié),所以第二個四段內(nèi)存中的第三個就是Block_descriptor_3
的首地址替饿,也就是signature
簽名信息的地址语泽,是個char *
類型,占8字節(jié)盛垦。打印結果為v8@?0
湿弦,所以這就是我們當前Block
的簽名。
簽名信息分析:
-
v
:返回值void
-
8
:占8位腾夯,也就是block本身占用的內(nèi)存空間 -
@?
:block簽名 -
0
:起始位置為0
我們再來看看有參數(shù)有返回值的Block
的簽名:
代碼:
NSString* (^block1)(int a, int b) = ^(int a, int b){
return [NSString stringWithFormat:@"%d---%d", a, b];
};
NSString * str = block1(1,2);
NSLog(@"字符串的值是:%@", str);
內(nèi)存讀取結果:
此時的簽名變成了@"NSString"16@?0i8i12
簽名信息分析:
-
@"NSString":
返回值為OC
類NSString
-
16
:占用16字節(jié) -
@?
:block的簽名 -
0i8i12
:起始位置為0,block蔬充,i
為分隔符蝶俱,8是第一個參數(shù)的起始位置也就是int a
,12 是第一個參數(shù)的起始位置也就是int b
打印一下簽名
通過[NSMethodSignature signatureWithObjCTypes:"@?"]
通過打印我們可以看到isBlock
PS:其實我們直接po 打印也可以看到block的簽名:
結論:
block
的簽名為@?
2.3 Block 三層拷貝 捕獲外界變量并修改的底層實現(xiàn)(__block)
在1.4.2
中我們編譯后的代碼中多了兩個函數(shù)__main_block_copy_0
和__main_block_dispose_0
饥漫,在那一節(jié)我們并沒有詳細的分析榨呆,下面我們就通過這兩個函數(shù)來詳細的說說在底層__block
是個啥。
首先我們在__main_block_copy_0
函數(shù)中可以看到其在內(nèi)部調(diào)用了_Block_object_assign
函數(shù)庸队,那么我們就去libclosure
中搜索一下這個函數(shù):
2.3.1 _Block_object_assign
/*******************************************************
Entry points used by the compiler - the real API!
A Block can reference four different kinds of things that require help when the Block is copied to the heap.
1) C++ stack based objects
2) References to Objective-C objects
3) Other Blocks
4) __block variables
In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers. The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign. The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.
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.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)
So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.
When a __block variable is either a C++ object, an Objective-C object, or another Block then the compiler also generates copy/dispose helper functions. Similarly to the Block copy helper, the "__block" copy helper (formerly and still a.k.a. "byref" copy helper) will do a C++ copy constructor (not a const one though!) and the dispose helper will do the destructor. And similarly the helpers will call into the same two support functions with the same values for objects and Blocks with the additional BLOCK_BYREF_CALLER (128) bit of information supplied.
So the __block copy/dispose helpers will generate flag values of 3 or 7 for objects and Blocks respectively, with BLOCK_FIELD_IS_WEAK (16) or'ed as appropriate and always 128 or'd in, for the following set of possibilities:
__block id 128+3 (0x83)
__block (^Block) 128+7 (0x87)
__weak __block id 128+3+16 (0x93)
__weak __block (^Block) 128+7+16 (0x97)
********************************************************/
//
// 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 *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;
}
}
根據(jù)注釋我們總結如下:
- 在將塊復制到堆時积蜻,塊可以引用四種不同類型的需要幫助的東西。
- 1)基于c++棧的對象
- 2)引用Objective-C對象
- 3)其他模塊
- __block變量
- 當
block
或Block_byrefs
持有對象時彻消,它們的復制例程助手就會使用這個入口點 - 所以這個函數(shù)并不僅僅用于
__block
竿拆,對于很多從棧區(qū)拷貝到堆區(qū)的操作可能都會用到此函數(shù)
這個函數(shù)有三個參數(shù):
-
void *destArg
:捕獲對象的地址、 -
const void *object
:捕獲對象 -
flags
: flag標志
對于這三個參數(shù)從__block
處分析宾尚,我們可以從1.4.2
中的如下代碼:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
-
destArg
:&dst->a -
object
:src->a -
flags
:8
__main_block_impl_0和源碼:
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) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
在上一遍源碼丙笋,其他的就不多說了,已經(jīng)很顯而易見了煌贴,下面我們在看看_Block_object_assign
函數(shù)御板,該函數(shù)的核心就是通過flags
中的值去找出各種外界變量種類組合,種類代碼如下:
// 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
:對象 -
BLOCK_FIELD_IS_BLOCK
:block變量 -
BLOCK_FIELD_IS_BYREF
__block 修飾的變量 -
BLOCK_FIELD_IS_WEAK
:__weak 修飾的變量 -
BLOCK_BYREF_CALLER
:處理Block_byref內(nèi)部對象內(nèi)存的時候會加的一個額外標記牛郑,配合上面的枚舉一起使用
此處我們看看BLOCK_FIELD_IS_BYREF
也就是對應__block
時在函數(shù)內(nèi)部是怎么處理的:
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;
我們可以看到怠肋,其內(nèi)部調(diào)用的是_Block_byref_copy
函數(shù)
2.3.2 _Block_byref_copy
_Block_byref_copy源碼:
// 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 and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
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;
}
這個函數(shù)是__block
捕獲外界變量的操作內(nèi)存拷貝以及一些常規(guī)處理。
- 這里首先出初始化一個局部變量
src
存儲傳入的外部變量 - 然后判斷
block
的引用計數(shù)是否為0 - 如果不為0說明不是第一次拷貝淹朋,進入另一個分支判斷是否需要釋放(
free
)笙各,如果需要則調(diào)用latching_incr_int
函數(shù)增加引用計數(shù) - 如果以上都不滿足直接返回
src->forwarding
- 如果是0就說明是第一次拷貝
- 首先創(chuàng)建一個一樣大小的
Block_byref
變量copy
- 給
copy
賦一些值,這里有一處重要的操作就是通過對copy
的forwarding
和src
的forwarding
同時指向copy
來達到變量的指針統(tǒng)一瑞你,以達到修改變量值時酪惭,達到同時修改的目的。- 下面判斷該
block
是否需要銷毀者甲,如果需要就進行一些賦值操作 - 還會判斷
block
是否有擴展信息春感,如果有也會進行一些賦值操作 - 最后調(diào)用
src2->byref_keep
,那么這個byref_keep
是什么呢?我們進一步分析
- 下面判斷該
- 首先創(chuàng)建一個一樣大小的
在分析byref_keep
前我們先看看latching_incr_int
函數(shù)鲫懒,源碼如下:
latching_incr_int源碼:
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
latching_incr_int
函數(shù)中的判斷就不多說了嫩实,這里說一下為啥要加2
,因為記錄引用計數(shù)是在flag
的第二位中窥岩,第一位是記錄block
是否釋放的甲献,所以加2
想當于第二位加1.
byref_keep:
由于byref_keep
是一個BlockByrefKeepFunction
函數(shù)指針類型的屬性,所以byref_keep
并不是函數(shù)名颂翼,byref_keep
所在的結構體:
//__Block 修飾的結構體
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
//__Block 修飾的結構體 byref_keep 和 byref_destroy 函數(shù) - 來處理里面持有對象的保持和銷毀
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
那么到這里就算斷了嗎晃洒?那肯定不是的,我們?nèi)?code>1.4.2中Clang
編譯后的代碼中去尋找__Block_byref_a_0
這個結構體中看看朦乏,這里的第五個參數(shù)為123
球及,因為這個是int
類型的外部變量,我們在外部賦值的時候為123
呻疹。下面我們換個字符串試試吃引。
OC代碼:
__block NSString *block_test = [NSString stringWithFormat:@"block_test"];
void (^block)(void) = ^{
block_test = @"block_test_block";
NSLog(@"LG_Block - %@",block_test);
};
block();
編譯后__Block_byref_block_test_0部分
__attribute__((__blocks__(byref))) __Block_byref_block_test_0 block_test = {
(void*)0,
(__Block_byref_block_test_0 *)&block_test,
33554432,
sizeof(__Block_byref_block_test_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"),
sel_registerName("stringWithFormat:"),
(NSString *)&__NSConstantStringImpl__var_folders_0r_7cq1c39116927bt9x0bjsbtm0000gn_T_main_0ca87c_mi_0)};
這時我們看到__Block_byref_block_test_0
第五個參數(shù)為__Block_byref_id_object_copy_131
,也就是對應byref_keep
的位置因為Block_byref
有四個屬性刽锤,所以Block_byref_2
的第一屬性就對應著這里面的第五個參數(shù)镊尺。
我們在Clang
編譯后的代碼中搜索__Block_byref_id_object_copy_131
,其實現(xiàn)如下:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
我們發(fā)現(xiàn)__Block_byref_id_object_copy_131
內(nèi)部也是調(diào)用的_Block_object_assign
函數(shù)并思,但是參數(shù)確是偏移了40位的庐氮,我們知道這里是傳入的參數(shù)是捕獲的外界變量生成的結構體,對于這次編譯生成的結構體源碼如下:
struct __Block_byref_block_test_0 {
void *__isa;
__Block_byref_block_test_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *block_test;
};
由以上代碼我們可以看出前面的地址和是8+8+4+4+8+8 = 40
纺荧,所以偏移40位后就是block_test
的地址旭愧,也就是取的外界變量的值。所以這就是將外界捕獲的變量在通過_Block_object_assign
進行拷貝處理一次宙暇。也驗證了我們一開始時說_Block_object_assign
并不僅僅是處理__block
的输枯。
2.3.3 _Block_copy
對于block
類型的變量會調(diào)用_Block_copy
函數(shù)進行處理,下面我們就看看_Block_copy
函數(shù)占贫,源碼如下:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
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.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
此處源碼我們在一開始就上過桃熄,那時只是由該函數(shù)引入了對Block
底層結構的分析,現(xiàn)在我們分析一下該方法:
- 首先判斷是需要釋放的型奥,也就是堆區(qū)
block
則調(diào)用latching_incr_int
函數(shù)直接進行引用計數(shù)的增加瞳收,該函數(shù)在上面分析過,這里就不多說了 - 然后判斷是不是全局
block
厢汹,如果是就直接返回 - 最后也就是棧區(qū)
block
- 根據(jù)
block
的大小申請一塊堆區(qū)空間 - 將棧區(qū)
block
移動到堆區(qū)申請的空間 - 對
invoke
進行賦值 - 對
flags
進行賦值螟深,是否需要釋放,引用計數(shù)等 - 調(diào)用
_Block_call_copy_helper
函數(shù)處理Block_descriptor_2
的copy
動作 - 將
isa
設置為_NSConcreteMallocBlock
也就是堆block
- 根據(jù)
_Block_call_copy_helper源碼:
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); // do fixup
}
2.3.4 小結
至此我們對Block
的拷貝就分析完了烫葬,總結如下:
- 首先會調(diào)用
_Block_copy
函數(shù)將棧區(qū)block
拷貝到堆區(qū)(一層) - 對于使用
__block
修飾的外界變量底層會生成一個__Block_byref_xxx_0
的結構體 - 對于該結構體首先會調(diào)用
_Block_object_assign
函數(shù)對齊flags判斷進入不同分支處理界弧,這里就是BLOCK_FIELD_IS_BYREF
對應__block
- 在分支中會調(diào)用
_Block_byref_copy
函數(shù)凡蜻,函數(shù)內(nèi)部會拷貝一個一樣大小的結構體,并且將變量指針指向同一區(qū)域垢箕,已達到修改值時相同的目的(二層) - 最后會通過
Block_byref_2
中的byref_keep
屬性記錄的函數(shù)指針內(nèi)調(diào)用_Block_object_assign
函數(shù)划栓,傳入__Block_byref_xxx_0
的結構體的外界變量的值進行又一次拷貝,這個值是通過指針偏移找到的(三層) - 如果外界變量不是對象時条获,例如
int
則直接記錄其值忠荞,不會進行最后一次拷貝操作 - 對于
_Block_copy
函數(shù)內(nèi)對block
類型的拷貝:- 全局
block
不要拷貝 - 棧區(qū)
block
需要拷貝到堆區(qū) - 堆區(qū)
block
增加引用計數(shù)即可
- 全局
2.4 Block的釋放
在上一節(jié)中我們提到,編譯后的代碼中會多出兩個函數(shù)帅掘,其中我們分析了__main_block_copy_0
委煤,還剩下一個__main_block_dispose_0
,下面我們就來看看__main_block_dispose_0
都做了什么锄开?
__main_block_dispose_0源碼:
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_test, 8/*BLOCK_FIELD_IS_BYREF*/);}
由__main_block_dispose_0
函數(shù)我們可以看出其內(nèi)部調(diào)用了_Block_object_dispose
函數(shù)素标,所以我們就來到libclosure
源碼中搜索一下這個函數(shù),源碼如下:
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
通過源碼我們可以看到它與_Block_object_assign
的實現(xiàn)方式是一致的萍悴,都是通過一個switch
函數(shù)來進行匹配不同的情況:
2.4.1 釋放__block 修飾的變量(BLOCK_FIELD_IS_BYREF)
當需要釋放__block
修飾的變量時會調(diào)用_Block_byref_release
函數(shù),源碼實現(xiàn)如下:
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
源碼分析:
- 首先獲取到要釋放的變量寓免,并取消引用轉(zhuǎn)發(fā)的指針癣诱,因為在賦值的時候是與外界變量指向同一空間的
- 判斷是否需要釋放,如果不需要就執(zhí)行完畢了
- 如果需要釋放
- 獲取引用計數(shù)
- 調(diào)用
latching_decr_int_should_deallocate
函數(shù)判斷是否應該釋放- 應該釋放的話就判斷
flags
中是否有拷貝/釋放輔助函數(shù)- 如果有的話就獲取一個臨時變量
byref2
調(diào)用byref_destroy
屬性保存的函數(shù)
- 如果有的話就獲取一個臨時變量
- 最后釋放
byref
- 應該釋放的話就判斷
關于byref_destroy
保存的函數(shù)袜香,實現(xiàn)原理與byref_keep
是一致的撕予,我們來到編譯后的eC++
文件中查看,byref_destroy
屬性保存的函數(shù)是__Block_byref_id_object_dispose_131
蜈首,其代碼如下:
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
通過__Block_byref_id_object_dispose_131
的代碼我們可以看出实抡,再其內(nèi)部調(diào)用的是_Block_object_dispose
函數(shù),同樣也是偏移了40
欢策,如果不是對象類型吆寨,比如int
是沒有保存這個函數(shù)的,原理同拷貝原理踩寇,拷貝的時候分三層拷貝啄清,釋放的時候也就要三層釋放。
上面提到的latching_decr_int_should_deallocate
函數(shù)返回當前block
是否應該釋放俺孙,該函數(shù)的源碼如下:
latching_decr_int_should_deallocate源碼:
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return false; // latched high
}
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
return false; // underflow, latch low
}
int32_t new_value = old_value - 2;
bool result = false;
if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
new_value = old_value - 1;
result = true;
}
if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
return result;
}
}
}
- 這里通過一個
while循環(huán)
辣卒,不斷的判斷block
是否應該釋放 - 首先判斷
flags
與上BLOCK_REFCOUNT_MASK
等于BLOCK_REFCOUNT_MASK
,則返回false - 然后判斷兩個相與是否等于0睛榄,等于的話也會返回false
- 創(chuàng)建一個新
new_value
= old_value - 2荣茫,并定義一個bool
值result
等于false - 判斷舊
flags
與上(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)
等于2,則記錄result
等于true - 最后調(diào)用
OSAtomicCompareAndSwapInt
函數(shù)將新舊值比對交換场靴,如果交換成則返回result
啡莉,否則進入新的循環(huán)港准。
2.4.2 釋放Block變量(BLOCK_FIELD_IS_BLOCK)
當需要釋放block
變量時,需要調(diào)用_Block_release
函數(shù)票罐,其源碼實現(xiàn)如下:
// API entry point to release a copied Block
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
free(aBlock);
}
}
源碼分析:
- 首先創(chuàng)建一個局部的的
block
變量叉趣,如果為空直接return
- 如果
block
是全局的,則直接return
- 如果
block
不需要釋放也直接return
- 如果都不是則調(diào)用
latching_decr_int_should_deallocate
判斷是否能夠釋放该押,函數(shù)分析在上一節(jié)已經(jīng)分析過了- 如果可以釋放就調(diào)用
_Block_call_dispose_helper
函數(shù)疗杉,獲取descriptor_2
中dispose
存儲的是否函數(shù)進行調(diào)用 - 然后還會調(diào)用
_Block_destructInstance
,這里沒有相關實現(xiàn)蚕礼,應該是沒開源吧 - 最后
free
局部變量
- 如果可以釋放就調(diào)用
3. 總結
-
Block
是一個匿名函數(shù)烟具,也是一個對象,在底層是一個Block_layout
-
Block
需要調(diào)用是因為Block
代碼塊在底層是一個函數(shù)奠蹬,要想讓其執(zhí)行朝聋,所以需要調(diào)用 -
Block
捕獲外界變量的時候會生成一個同名的中間變量,取獲取到的時候的值 -
Block
使用外界變量的時候會生成一個__Block_byref_xxx_0
的結構體 -
Block
的簽名是@?
-
Block
通過__block
訪問外界變量的時候會有三層拷貝- 首先是
block
從椂谠辏拷貝到堆 - 將修飾的對象轉(zhuǎn)話為一個結構體冀痕,將其拷貝到堆內(nèi)存
- 將修飾的對象的內(nèi)存地址也進行拷貝
- 首先是
-
Block
的釋放相當于拷貝的反向,拷貝的東西都需要釋放的