Block的底層內(nèi)部結(jié)構(gòu)圖

Block的結(jié)構(gòu)中首地址指向的就是isa指針,因此Blcok其實(shí)也是我們OC中的對(duì)象。通過(guò)編譯器的處理成C++底層的代碼時(shí),Block就是一個(gè)結(jié)構(gòu)體,其代碼結(jié)構(gòu)如下
struct __main_block_impl_0 {
// impl結(jié)構(gòu)體
struct __block_impl {
void *isa; //block的isa指針
int Flags; //位移枚舉標(biāo)記(標(biāo)記desc中有無(wú) copy , dispose方法,有無(wú)方法簽名字符 Signature 等...)
int Reserved;
void *FuncPtr; //實(shí)現(xiàn)block的功能函數(shù)
} impl ;
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; //block 的 內(nèi)存大小
/** 以下兩個(gè)函數(shù)是在 isa 指針指向 _NSConcreteMallocBlock時(shí)才會(huì)有 **/
void (*copy)(void);
void (*dispose)(void);
/** 以下字符串是在impl.flag 包含((1 << 30)這個(gè)值是才有的變量),對(duì)應(yīng)oc中的方法簽名NSMethodSignature**/
const char *signatureStr;
} * Desc;
/** 以下都是block捕獲的變量 ,變量順序和是否捕獲進(jìn)來(lái)根據(jù)block的定義來(lái)決定 ,這里只是簡(jiǎn)單舉例**/
struct __Block_byref_var_0 *var ; // __block變量
TestClass *__strong strongTestVar ; // strong 變量
TestClass *__weak weakTestVar ; // weak 變量
int a ; //局部普通數(shù)據(jù)類型
int *b ;//局部靜態(tài)變量
/**全局靜態(tài)變量是直接通過(guò)變量的地址訪問的不需要捕獲進(jìn)來(lái)*/
}
isa - Block 的類型(isa指針的指向)分為 3種
_NSConcreteStackBlock: 只用到外部局部變量 , 且沒有強(qiáng)指針引用的block , 其實(shí)質(zhì)上就是函數(shù)棧上的局部變量,在當(dāng)前函數(shù)調(diào)用完后:(恢復(fù)??臻g的時(shí)候),就會(huì)被釋放掉。_NSConcreteGlobalBlock: 完全沒有用到外部變量 ,或只用到全局變量、靜態(tài)變量的block ,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束。Block 訪問全局變量 或 靜態(tài)變量 都是通過(guò)捕獲他們的地址進(jìn)行內(nèi)容訪問的,因?yàn)檫@些變量從定義的那一刻開始就確定了其地址,因此可以通過(guò)指針傳遞來(lái)捕獲到block內(nèi)部進(jìn)行訪問。而捕獲普通局部變量就不一樣,局部變量在函數(shù)返回后其內(nèi)存有可能會(huì)被會(huì)回收掉,所以是不能通過(guò)捕獲局部變量的地址到block訪問的,而是通過(guò)值傳遞來(lái)傳進(jìn)block內(nèi)部
_NSConcreteMallocBlock(估計(jì)是我們最常解除的block類型了) :特點(diǎn)是有強(qiáng)指針引用,或者被帶有copy修飾的屬性引用,或者作為函數(shù)返回值返回時(shí)。
Flags : 這是一個(gè)位移枚舉的變量,標(biāo)記著block的一些屬性,比如
- 結(jié)構(gòu)體的
Desc中有無(wú)copy個(gè)dispose函數(shù) (1 << 25) - 結(jié)構(gòu)體的
Desc中有無(wú)signatureStrtype encodings (char * 類型字符串) (1<<30) - ......
FuncPtr : 就是你定義block的內(nèi)部邏輯實(shí)現(xiàn)函數(shù)的指針。通過(guò)編譯器把OC的代碼處理成c語(yǔ)言的函數(shù)后在block初始化時(shí),用這個(gè)變量記錄函數(shù)的指針地址,當(dāng)block被調(diào)用時(shí)就是執(zhí)行這個(gè)函數(shù)指針指向的函數(shù)。
Desc. Block_size : block的內(nèi)存占用空間的大小
Desc -> copy + dispose 函數(shù) :block用于管理自身內(nèi)存的函數(shù)
......
更詳細(xì)的 block底層源碼實(shí)現(xiàn) 以及 __block變量的原理 推薦閱讀 深入研究Block捕獲外部變量和__block實(shí)現(xiàn)原理 這篇文章
清楚了Block的內(nèi)部結(jié)構(gòu)后,我們來(lái)看下如何理由 NSInvocation進(jìn)行調(diào)
用。
NSInvocation是一個(gè)OC中用來(lái)封裝消息發(fā)送的類,在Runtime的消息轉(zhuǎn)發(fā)的最后一個(gè)轉(zhuǎn)發(fā)步驟(Normal Forwarding)也有出現(xiàn)NSInvocation。
Normal Forwarding首先調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector這個(gè)方法,向調(diào)用者返回一個(gè)selector對(duì)應(yīng)的方法簽名類NSMethodSignature對(duì)象,如果沒有返回NSMethodSignature這個(gè)類對(duì)象的話 就會(huì)拋出找不到方法的錯(cuò)誤,否則,就會(huì)利用返回的NSMethodSignature對(duì)象 生成一個(gè)NSInvocation對(duì)象傳進(jìn)- (void)forwardInvocation:(NSInvocation *)anInvocation方法中完成消息轉(zhuǎn)發(fā)機(jī)制的最后一步。
首先根據(jù)上面分析的Block內(nèi)部定義一個(gè)結(jié)構(gòu)體 ,方便我們對(duì)block進(jìn)行內(nèi)部訪問。
struct BlockLayout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src); // (1<<25)
void (*dispose)(void *src);
const char *signature; // (1<<30)
} *descriptor;
// 捕獲的變量
};
enum {
DescFlagsHasCopyDispose = (1 << 25),
DescFlagsIsGlobal = (1 << 28),
DescFlagsHasSignature = (1 << 30)
};
typedef int BlockDescFlags;
然后定義一個(gè)簡(jiǎn)單的block
void(^testBlock)(int a , int b) = ^(int a , int b){
NSLog(@"成功調(diào)用了 block");
NSLog(@"參數(shù)1 -> a = %d , 參數(shù)2 -> b = %d" , a , b);
};
下面開始對(duì)Block內(nèi)部進(jìn)行訪問,獲取去signature(const char * )后生成NSInvoction并傳參調(diào)用。
//強(qiáng)轉(zhuǎn)為自定義的block結(jié)構(gòu)體指針
struct BlockLayout * blockLayoutPointer = (__bridge struct BlockLayout *)testBlock;
int flags = blockLayoutPointer -> flags;
if (flags & BlockDescFlagsHasSignature) { //有signature字符串
void * signaturePoint = blockLayoutPointer -> descriptor;
signaturePoint += sizeof(unsigned long int); //reserved
signaturePoint += sizeof(unsigned long int); //size
if (flags & BlockDescFlagsHasCopyDispose) {
signaturePoint += sizeof(void (*)(void *dst , void *src)); //copy
signaturePoint += sizeof(void (*)(void *src)); //dispose
}
//拿到 signature 字符串內(nèi)容
const char * signatureStr = (* (const char **) signaturePoint);
NSMethodSignature * blockSignature = [NSMethodSignature signatureWithObjCTypes:signatureStr];
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:blockSignature];
invocation.target = testBlock;
//將要傳緊block的參數(shù)
int param1 = 10 ;
int param2 = 20 ;
// block(type encodeings 為 @?) 對(duì)應(yīng)的 NSInvocation 第一個(gè)參數(shù)為 block本身
// SEL(type encodeings 為 :) 對(duì)應(yīng)的 NSInvocation 第一個(gè)參數(shù)為 selector 的 調(diào)用者(targat type encodeings 為 @) ,第二個(gè)參數(shù)這是 _cmd (方法本身類型為SEL)
[invocation setArgument:¶m1 atIndex:1];
[invocation setArgument:¶m2 atIndex:2];
[invocation invoke];
}
看下打印 ,成功地利用NSInvocation對(duì)象調(diào)用了 Block。
2018-09-03 15:10:55.182181+0800 BlockWithNSInvocation[10694:872743] 成功調(diào)用了 block
2018-09-03 15:10:55.182430+0800 BlockWithNSInvocation[10694:872743] 參數(shù)1 -> a = 10 , 參數(shù)2 -> b = 20
Program ended with exit code: 0
PS:
類型強(qiáng)轉(zhuǎn)其實(shí)并沒有改變目標(biāo)變量的實(shí)際內(nèi)存的數(shù)據(jù),類型強(qiáng)轉(zhuǎn)其實(shí)就是告訴編譯器 目標(biāo)變量 是我強(qiáng)轉(zhuǎn)的類型數(shù)據(jù),你對(duì)這個(gè)變量訪問時(shí)按照我指定的變量類型來(lái)訪問即可。80e8b56c-2c3c-4a30-a9b0-d4096893ebf0.png
