Block經過Clang編譯器編譯成C++語言後,能夠看到它實際上是一個結構體。結構及成員變量的構成以下圖所示:bash
Block的結構中首地址指向的就是isa指針,所以Blcok其實也是咱們OC中的對象。經過編譯器的處理成C++底層的代碼時,Block就是一個結構體,其代碼結構以下函數
struct __main_block_impl_0 {
// impl結構體
struct __block_impl {
void *isa; //block的isa指針
int Flags; //位移枚舉標記(標記desc中有無 copy , dispose方法,有無方法簽名字符 Signature 等...)
int Reserved;
void *FuncPtr; //實現block的功能函數
} impl ;
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; //block 的 內存大小
/** 如下兩個函數是在 isa 指針指向 _NSConcreteMallocBlock時纔會有 **/
void (*copy)(void);
void (*dispose)(void);
/** 如下字符串是在impl.flag 包含((1 << 30)這個值是纔有的變量),對應oc中的方法簽名NSMethodSignature**/
const char *signatureStr;
} * Desc;
/** 如下都是block捕獲的變量 ,變量順序和是否捕獲進來根據block的定義來決定 ,這裏只是簡單舉例**/
struct __Block_byref_var_0 *var ; // __block變量
TestClass *__strong strongTestVar ; // strong 變量
TestClass *__weak weakTestVar ; // weak 變量
int a ; //局部普通數據類型
int *b ;//局部靜態變量
/**全局靜態變量是直接經過變量的地址訪問的不須要捕獲進來*/
}
複製代碼
isa - Block 的類型(isa指針的指向)分爲 3種ui
_NSConcreteStackBlock
: 只用到外部局部變量 , 且沒有強指針引用的block , 其實質上就是函數棧上的局部變量,在當前函數調用完後:(恢復棧空間的時候),就會被釋放掉。_NSConcreteGlobalBlock
: 徹底沒有用到外部變量 ,或只用到全局變量、靜態變量的block ,生命週期從建立到應用程序結束。PS : Block 訪問全局變量或靜態變量 都是經過捕獲他們的地址進行內容訪問的,由於這些變量從定義的那一刻開始就肯定了其地址,所以能夠經過指針傳遞來捕獲到block內部進行訪問。而捕獲普通局部變量就不同,局部變量在函數返回後其內存有可能會被會回收掉,因此是不能經過捕獲局部變量的地址到block訪問的而是經過值傳遞來傳進block內部spa
_NSConcreteMallocBlock
(估計是咱們最常解除的block類型了) :特色是有強指針引用,或者被帶有copy修飾的屬性引用,或者做爲函數返回值返回時。
Flags : 這是一個位移枚舉的變量,標記着block的一些屬性,好比3d
Desc
中有無 copy
個 dispose
函數 (1 << 25)Desc
中有無 signatureStr
type encodings (char * 類型字符串) (1<<30)FuncPtr : 就是你定義block的內部邏輯實現函數的指針。經過編譯器把OC的代碼處理成c語言的函數後在block初始化時,用這個變量記錄函數的指針地址,當block被調用時就是執行這個函數指針指向的函數。指針
Desc. Block_size : block的內存佔用空間的大小code
Desc -> copy
+ dispose
函數 :block用於管理自身內存的函數orm
......cdn
更詳細的 block底層源碼實現 以及 __block變量的原理 推薦閱讀 深刻研究Block捕獲外部變量和__block實現原理 這篇文章對象
清楚了Block的內部結構後,咱們來看下如何理由 NSInvocation進行調 用。
NSInvocation
是一個OC中用來封裝消息發送的類,在Runtime
的消息轉發的最後一個轉發步驟(Normal Forwarding)也有出現NSInvocation
。Normal Forwarding
首先調用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
這個方法,向調用者返回一個selector
對應的方法簽名類NSMethodSignature
對象,若是沒有返回NSMethodSignature
這個類對象的話 就會拋出找不到方法的錯誤,不然,就會利用返回的NSMethodSignature
對象 生成一個NSInvocation
對象傳進- (void)forwardInvocation:(NSInvocation *)anInvocation
方法中完成消息轉發機制的最後一步。
首先根據上面分析的Block內部定義一個結構體 ,方便咱們對block進行內部訪問。
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;
複製代碼
而後定義一個簡單的block
void(^testBlock)(int a , int b) = ^(int a , int b){
NSLog(@"成功調用了 block");
NSLog(@"參數1 -> a = %d , 參數2 -> b = %d" , a , b);
};
複製代碼
下面開始對Block內部進行訪問,獲取去signature(const char * )後生成NSInvoction並傳參調用。
//強轉爲自定義的block結構體指針
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 字符串內容
const char * signatureStr = (* (const char **) signaturePoint);
NSMethodSignature * blockSignature = [NSMethodSignature signatureWithObjCTypes:signatureStr];
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:blockSignature];
invocation.target = testBlock;
//將要傳緊block的參數
int param1 = 10 ;
int param2 = 20 ;
// block(type encodeings 爲 @?) 對應的 NSInvocation 第一個參數爲 block自己
// SEL(type encodeings 爲 :) 對應的 NSInvocation 第一個參數爲 selector 的 調用者(targat type encodeings 爲 @) ,第二個參數這是 _cmd (方法自己類型爲SEL)
[invocation setArgument:¶m1 atIndex:1];
[invocation setArgument:¶m2 atIndex:2];
[invocation invoke];
}
複製代碼
看下打印 ,成功地利用NSInvocation對象調用了 Block。
2018-09-03 15:10:55.182181+0800 BlockWithNSInvocation[10694:872743] 成功調用了 block
2018-09-03 15:10:55.182430+0800 BlockWithNSInvocation[10694:872743] 參數1 -> a = 10 , 參數2 -> b = 20
Program ended with exit code: 0
複製代碼
關於類型強轉
類型強轉其實並無改變目標變量的實際內存的數據,類型強轉其實就是告訴編譯器 目標變量 是我強轉的類型數據,你對這個變量訪問時按照我指定的變量類型來訪問便可。