iOS - 對 block 實現的探究
看 Aspects 原始碼的時候,發現了一段程式碼
typedef struct _AspectBlock {
__unused Class isa;
AspectBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _AspectBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires AspectBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires AspectBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *AspectBlockRef;
// ......
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
AspectBlockRef layout = (__bridge void *)block;
// .......
}
當時不理解這個程式碼是什麼意思,因此想要探究 block 的本質。以下是一點查閱資料並且自己測試後的一點見解。
1. 要想看 block 的底層實現,首先將 OC 程式碼轉換成 C++ 程式碼
新建 block.m
檔案新增以下 OC 程式碼:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
__block int b = 20;
NSString *str = @"str";
__weak NSString *weakStr = @"weakStr";
void(^block)(int) = ^(int i){
i;
a;
b;
str;
weakStr;
};
block(1);
}
return 0;
}
上述程式碼定義了各種型別的變數用於測試
終端 cd 到 block.m
根目錄執行一下命令進行轉換
clang -rewrite-objc block.m
這個時候會報 error
cannot create __weak reference because the current deployment target does not support weak references
在 stack overflow
上找到這個問題解決方案,通過以下命令進行轉換
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations block.m
這時候可以發現在當前目錄會生成對應的 C++ 檔案 block.cpp
,通過這個檔案可以看到 block 在 C++ 中的實現,更好的理解 block 機制
找到其中對應 main 函式的程式碼
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_0__w7c3mlv94_10qs2mxt1kk0980000gn_T_block_3ee913_mi_0;
__attribute__((objc_ownership(none))) NSString *weakStr = (NSString *)&__NSConstantStringImpl__var_folders_0__w7c3mlv94_10qs2mxt1kk0980000gn_T_block_3ee913_mi_1;
void(*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, str, weakStr, (__Block_byref_b_0 *)&b, 570425344));
((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1);
}
return 0;
}
2. 解析 C++ 檔案
2.1 __Block_byref_b_0
OC 程式碼
__block int b = 20;
對應 C++ 程式碼
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
實際上 b
就是以下型別
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
可以看出被 __block
修飾的 b
是一個與 OC 記憶體結構相容的 struct
__isa
: 預設為(void*)0
__forwarding
: 在建構函式中傳入的是(__Block_byref_b_0 *)&b
,是一個指向b
的指標,在下面 block 的 C++ 實現(2.2 __main_block_impl_0
)中可以看到建構函式傳入的就是該指標,因此用__block
修飾的變數實際傳入 block 的是一個指向該變數的指標,因此可以對其進行修改__size
: 大小b
:b 的值 20
2.2 __main_block_impl_0
OC 程式碼
void(^block)(int) = ^(int i){
i;
a;
b;
str;
weakStr;
};
對應 C++ 程式碼
void(*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, str, weakStr, (__Block_byref_b_0 *)&b, 570425344));
實際上 block
就是以下型別
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
NSString *__strong str;
NSString *__weak weakStr;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSString *__strong _str, NSString *__weak _weakStr, __Block_byref_b_0 *_b, int flags=0) : a(_a), str(_str), weakStr(_weakStr), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到 block
實際也是一個結構體,內部包含一個 __block_impl
的結構體、__main_block_desc_0
結構體指標,以及用到的外部變數 a
,b
,str
,weakStr
和一個建構函式
通過觀察建構函式,可以看出外部變數傳遞到 block 中時,只有用 __block
修飾過的 b
傳入的是 __Block_byref_b_0 *
指標,對 b
的修改操作都是通過 _b->__forwarding
來直接對原始b
資料進行修改。而其他未用 __block
只是進行賦值操作,對 a
,str
, weakStr
的修改無法影響到 block 結構體內部的對應的屬性的值。
因此會產生以下程式碼的差異
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
__block int b = 10;
void(^block)(void) = ^ {
NSLog(@"%d,%d", a, b);
};
a = 20;
b = 20;
block(); // 輸出 10,20
}
return 0;
}
2.3 __block_impl
__block_impl
結構體的定義如下,儲存 block 的實現
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
-
isa
: 表示一個類物件指標,指向block
的類。我們可以看到在__main_block_impl_0
建構函式中有以下程式碼impl.isa = &_NSConcreteStackBlock;
isa
被賦值為&_NSConcreteStackBlock
,指向類__NSStackBlock__
的指標,說明了該 block 的是一個存放在棧區的 block,下面3. Block 型別
有對 block 的型別進行介紹 Flags
:標記位,標記 desc(2.4 __main_block_desc_0
) 中有無 copy , dispose方法,有無方法簽名字元 signature,layout 等FuncPtr
:指標指向 block 的具體實現,看main
方法中傳入的引數為方法__main_block_func_0
2.4 __main_block_desc_0
__main_block_desc_0
結構體的定義如下,儲存 block 描述資訊
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*);
// 以下內容在本例中沒有,但是若 __block_impl 中 Flags 有標記位 1 << 30 則會有
// const char *signature;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
-
reserved
: 0 -
Block_size
:block 大小 -
copy
:copy 傳入的是方法__main_block_copy_0
,實現的功能就是當 block 進行copy
時,會呼叫該方法。具體實現如下
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->weakStr, (void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
-
dispose
:dispose 傳入的是方法__main_block_dispose_0
,實現的功能就是當 block 進行從堆中移除時呼叫。具體實現如下
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
-
signature
:在本例中沒有,但是若 block_impl(2.3 __block_impl
) 中 Flags 有標記位 1 << 30 則會有,對方法引數的簽名,對應NSMethodSignature
2.5 __main_block_func_0
OC 程式碼
{
i;
a;
b;
str;
weakStr;
};
對應 C++ 程式碼,方法 __main_block_func_0 定義如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
int a = __cself->a; // bound by copy
NSString *__strong str = __cself->str; // bound by copy
NSString *__weak weakStr = __cself->weakStr; // bound by copy
i;
a;
(b->__forwarding->b);
str;
weakStr;
}
__cself
指向 block 物件,只有用 __block
修飾過的變數會用特殊的方式呼叫 b->__forwarding->b
3. Block 型別
block 型別有 _NSConcreteStackBlock
、 _NSConcreteGlobalBlock
和 _NSConcreteMallocBlock
,這 3 類的區別如下
_NSConcreteStackBlock | _NSConcreteGlobalBlock | _NSConcreteMallocBlock | |
---|---|---|---|
存放區域 | 棧區 | 全域性區(靜態區) | 堆區 |
型別說明 | block 宣告為一個區域性變數 | Block 宣告為一個全域性變數 | stackBlock copy 後生成的 block 為 NSMallocBlock型別 |
如下程式碼可以分別生成 3 種型別的 block
// _NSConcreteGlobalBlock
void(^block_global)(int) = ^(int i){};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// _NSConcreteStackBlock
^(int i){};
// _NSConcreteMallocBlock
void(^block_malloc)(int) = [^(int i){} copy];
}
return 0;
}
對應 C++ 程式碼
// _NSConcreteGlobalBlock
// void(^block_global)(int) = ^(int i){};
struct __block_global_block_impl_0 {
struct __block_impl impl;
struct __block_global_block_desc_0* Desc;
__block_global_block_impl_0(void *fp, struct __block_global_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// _NSConcreteStackBlock
// ^(int i){};
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;
}
};
// _NSConcreteMallocBlock
// void(^block_malloc)(int) = [^(int i){} copy];
void(*block_malloc)(int) = (void (*)(int))((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA)), sel_registerName("copy"));
因為 block 建立後預設會在棧區,因此對 block 進行賦值的時候要使用 copy
生成一個存放在堆區的 block,避免 block 釋放後呼叫 block。用 @property
宣告一個 block 經常用 copy
關鍵字
但是,在 ARC 環境下,卻有所改變。在 ARC 環境下,將 block 賦值給一個 __strong
物件,會自動對其進行 copy
,因此在 ARC 環境下 @property
宣告一個 block 經常用 strong
關鍵字也是沒什麼問題的
在 ARC 情況下還有以下情況會自動將 block copy 到堆區
- block 作為函式返回值
- 將 block 賦值給__strong 物件
- Cocoa API 中方法名含有 usingBlock 的方法使用 block
- GCD 中使用的 block
相關文章
- iOS Block探究iOSBloC
- iOS - Block探究系列一iOSBloC
- iOS底層原理 - Block本質探究iOSBloC
- 探索iOS中Block的實現原理iOSBloC
- iOS中Block實現原理的全面分析iOSBloC
- iOS block巢狀block中weakify的使用iOSBloC巢狀
- block實現原理BloC
- 理解 Block 實現原理BloC
- 使用 Block 實現 KVOBloC
- iOS14剪下板探究,淘寶實現方法分析iOS
- iOS block 反向傳值iOSBloC
- iOS Block淺淺析iOSBloC
- Block學習⑤--block對物件變數的捕獲BloC物件變數
- iOS 中的 block 是如何持有物件的iOSBloC物件
- iOS探索:Block解析淺談iOSBloC
- iOS Block學習筆記iOSBloC筆記
- Object-C語言Block的實現方式ObjectC語言BloC
- iOS 深入探究 AutoreleasePooliOS
- Objective-C block 實現機制ObjectBloC
- iOS初級開發筆記:Block回撥,實現簡單的繫結支付寶邏輯iOS筆記BloC
- Promise原理探究及實現Promise
- 關於IOS物件的小事的探究iOS物件
- Vue原始碼探究-核心類的實現Vue原始碼
- iOS-block本質是什麼?iOSBloC
- display:block display:inline-block 的屬性、呈現和作用BloCinline
- UITableView的原理——探究及重新實現程式碼UIView
- MG--探究KVO的底層實現原理
- 從零實現DNN 探究梯度下降的原理DNN梯度
- iOS | 用於解決迴圈引用的block timeriOSBloC
- iOS之Wifi開發探究iOSWiFi
- iOS底層原理探究-RunloopiOSOOP
- iOS底層原理探究-RuntimeiOS
- 執行時Hook所有Block方法呼叫的技術實現HookBloC
- iOS Block傳值、代理傳值、通知中心iOSBloC
- iOS奇思妙想之使用block替代通知iOSBloC
- Vue原始碼探究-資料繫結的實現Vue原始碼
- 深入探究immutable.js的實現機制(二)JS
- 深入探究Immutable.js的實現機制(一)JS