日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

浅析Block的内部结构 , 及分析其是如何利用 NSInvocation 进行调用

發布時間:2024/4/14 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅析Block的内部结构 , 及分析其是如何利用 NSInvocation 进行调用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Block通過Clang編譯器編譯成C++語言后,可以看到它其實是一個結構體。結構及成員變量的構成如下圖所示:

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種

  • _NSConcreteStackBlock: 只用到外部局部變量 , 且沒有強指針引用的block , 其實質上就是函數棧上的局部變量,在當前函數調用完后:(恢復棧空間的時候),就會被釋放掉。
  • _NSConcreteGlobalBlock: 完全沒有用到外部變量 ,或只用到全局變量、靜態變量的block ,生命周期從創建到應用程序結束。
  • PS : Block 訪問全局變量或靜態變量 都是通過捕獲他們的地址進行內容訪問的,因為這些變量從定義的那一刻開始就確定了其地址,因此可以通過指針傳遞來捕獲到block內部進行訪問。而捕獲普通局部變量就不一樣,局部變量在函數返回后其內存有可能會被會回收掉,所以是不能通過捕獲局部變量的地址到block訪問的而是通過值傳遞來傳進block內部

  • _NSConcreteMallocBlock (估計是我們最常解除的block類型了) :特點是有強指針引用,或者被帶有copy修飾的屬性引用,或者作為函數返回值返回時。
  • Flags : 這是一個位移枚舉的變量,標記著block的一些屬性,比如

    • 結構體的Desc中有無 copy個 dispose函數 (1 << 25)
    • 結構體的Desc中有無 signatureStr type encodings (char * 類型字符串) (1<<30)
    • ......

    FuncPtr : 就是你定義block的內部邏輯實現函數的指針。通過編譯器把OC的代碼處理成c語言的函數后在block初始化時,用這個變量記錄函數的指針地址,當block被調用時就是執行這個函數指針指向的函數。

    Desc. Block_size : block的內存占用空間的大小

    Desc -> copy + dispose 函數 :block用于管理自身內存的函數

    ......

    更詳細的 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); //reservedsignaturePoint += sizeof(unsigned long int); //sizeif (flags & BlockDescFlagsHasCopyDispose) {signaturePoint += sizeof(void (*)(void *dst , void *src)); //copysignaturePoint += 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:&param1 atIndex:1];[invocation setArgument:&param2 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 復制代碼

    關于類型強轉

    類型強轉其實并沒有改變目標變量的實際內存的數據,類型強轉其實就是告訴編譯器 目標變量 是我強轉的類型數據,你對這個變量訪問時按照我指定的變量類型來訪問即可。

    總結

    以上是生活随笔為你收集整理的浅析Block的内部结构 , 及分析其是如何利用 NSInvocation 进行调用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。