深入解構iOS的block閉包實現(xiàn)原理

在iOS4出來后,蘋果公司在OC中推出了block機制(也許更早就有了)。并且在后續(xù)的版本中大量的推廣和使用了這項技術,比如對視圖動畫API的改版,比如GCD技術等等。block技術并不是什么新技術,他的本質就是閉包功能在iOS上的實現(xiàn)而已。而閉包功能在其他很多語言中都有實現(xiàn),比如JAVA中接口的匿名實現(xiàn)。用閉包可以解決那些執(zhí)行邏輯和上下文環(huán)境解耦的場景,如果從設計模式的角度來考慮的話閉包就是一種策略模式(Strategy)的實現(xiàn)。
本文并不探討如何應用block,而是探討OC的block機制是如何實現(xiàn)的。從代碼的角度來說block的出現(xiàn)和我們平時基于函數(shù)和類方法的編程方式不太一致,有時候甚至不好去理解,因為他可以在我們的代碼塊中定義代碼塊,而且新定義的代碼塊又不會按函數(shù)內的指令順序去執(zhí)行。我們可以大膽的設想,如果是要你去實現(xiàn)一套block機制,你會怎么去做?這也是本文要探討的東西,只有你知道了OC實現(xiàn)block的內幕,你才能夠更好的利用他。
寫這篇文章來分析原理時我隱去了一些細節(jié),而且有些結構體的定義也和真實的有差異,但是總體是正確的,目的是為了更好的了解到本質的東西。我們先來看下面一段含有block的OC代碼:

//文件test.m
#import <Foundation/Foundation.h>
void test()
{
    //下面分別定義各種類型的變量
     int a = 10;                       //普通變量
    __block int b = 20;                //帶__block修飾符的block普通變量
    NSString *str = @"123"; 
    __block NSString *blockStr = str;  //帶__block修飾符的block OC變量
    NSString *strongStr = @"456";      //默認是__strong修飾的OC變量
    __weak NSString *weakStr = @"789"; //帶__weak修飾的OC變量
  
  //定義一個block塊并帶一個參數(shù)
    void (^testBlock)(int) = ^(int c){
         int  d = a + b + c;
         NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
     };

    a = 20;  //修改值不會影響testBlock內的計算結果
    b = 40;  //修改值會影響testBlock內的計算結果。
    testBlock(30);  //執(zhí)行block代碼。
}
  • 上面的代碼片段中,我們分別定義了:
  • 不帶修飾符的基本類型變量a
  • 帶__block修飾符的block變量b和blockStr
  • 默認帶__strong修飾符的變量strongStr
  • 帶__weak修飾符的變量weakStr

這些修飾符關鍵字的使用會對block塊內的代碼在運行時產(chǎn)生不同的影響。就上面的代碼片段而言當我們在編譯時,編譯器到底做了什么處理?如果能夠了解到編譯器的編譯過程,那么對我們掌握其實現(xiàn)機制就非常有幫助。幸好我們可以借助命令來看到這個中間的過程,您可以打開終端控制臺,并到test.m文件所在的路徑下執(zhí)行如下的命令:

clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations test.m

clang這個命令會在相同目錄下產(chǎn)生一個test.cpp的文件。這個文件是OC代碼的C++實現(xiàn)版本,因為我們知道C++是不支持閉包技術的,因此您可以通過查看test.cpp這個文件來了解到OC中的閉包技術到底是如何用函數(shù)和結構體來實現(xiàn)的。我們可以先來看看test.cpp的部分實現(xiàn):

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};
struct __Block_byref_blockStr_1 {
  void *__isa;
__Block_byref_blockStr_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *blockStr;
};

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int a;
  NSString *strongStr;
  NSString *weakStr;
  __Block_byref_b_0 *b; // by ref
  __Block_byref_blockStr_1 *blockStr; // by ref
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, NSString *_strongStr, NSString *_weakStr, __Block_byref_b_0 *_b, __Block_byref_blockStr_1 *_blockStr, int flags=0) : a(_a), strongStr(_strongStr), weakStr(_weakStr), b(_b->__forwarding), blockStr(_blockStr->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself, int c) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  __Block_byref_blockStr_1 *blockStr = __cself->blockStr; // bound by ref
  int a = __cself->a; // bound by copy
  NSString *strongStr = __cself->strongStr; // bound by copy
  NSString *weakStr = __cself->weakStr; // bound by copy



        int d = a + (b->__forwarding->b) + c;

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_3, d, strongStr, (blockStr->__forwarding->blockStr), weakStr);

    }
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->strongStr, (void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->blockStr, (void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakStr, (void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
  void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
void test()
{


    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_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_0;
    __attribute__((__blocks__(byref))) __Block_byref_blockStr_1 blockStr = {(void*)0,(__Block_byref_blockStr_1 *)&blockStr, 33554432, sizeof(__Block_byref_blockStr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, str};
    NSString *strongStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_1;
     NSString *weakStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_2;

    void (*testBlock)(int) = ((void (*)(int))&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, strongStr, weakStr, (__Block_byref_b_0 *)&b, (__Block_byref_blockStr_1 *)&blockStr, 570425344));

    a = 20;
    (b.__forwarding->b) = 40;
    ((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);

}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

上面的代碼對于某些同學來說也許太過晦澀了!不過沒有關系,我把上面的代碼進行簡化和處理,并去掉了一些次要的東西,然后簡化為如下的代碼:

//  每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是_block int b 的結構體定義:
struct Block_b {
    void *isa;    //固定為NULL
    Block_b *forwarding;  //指向真正的block對象變量。
    int flags;
    int size; //結構體的size
    int b;   //保存代碼中定義的變量值。
};

//每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是 __block NString *blockStr 的結構體定義:
struct Block_blockStr {
    void *isa;  //固定為NULL
    Block_blockStr *forwarding;  //指向真正的block對象變量
    int flags;
    int size;  //結構體的size
    NSString * blockStr;  //保存代碼中定義的變量值。
};


//每個block塊都會生成一個和OC類內存結構兼容的結構體和一個描述這個block塊信息描述的結構體
struct Block_testBlock {
    //所有block塊的固定部分,這也是一個OC類的內存結構。
    Class isa;  //block的OC類型
    int flags;
    int reserved;
    void *funcPtr;       //block塊函數(shù)的地址。
    Block_testBlock_Desc* desc;   //block的描述信息。
    
    //所有在block代碼塊內引用的外部數(shù)據(jù)都會成為結構體內的數(shù)據(jù)成員。
    int a;
    NSString * strongStr;
    NSString * __weak weakStr;
    Block_b *b;
    Block_blockStr *blockStr;
    
    //結構體的構造函數(shù)。
    Block_testBlock(void *_funcPtr, Block_testBlock_Desc *_desc, int _a, NSString * _strongStr, NSString * _weakStr, struct Block_b *_b, Block_blockStr *_blockStr, int _flags)
    {
        isa = &_NSConcreteStackBlock;  //根據(jù)具體的block類型賦值。
        flags = _flags;
        reserved = 0;
        funcPtr = _funcPtr;
        desc = _desc;
        a = _a;
        strongStr = _strongStr;
        weakStr = _weakStr;
        b = _b->forwarding;  //b保存真實的block變量的地址。
        blockStr = _blockStr->forwarding;  //blockStr保存真實的block變量的地址。
    }
};

//block塊信息描述的結構體定義,主要有block對象的尺寸,以及block中函數(shù)的參數(shù)信息,也就是參數(shù)的簽名信息。并生成一個全局的常量對象_testBlock_desc_DATA
struct Block_testBlock_Desc {
    unsigned long reserved;
    unsigned long size; //塊的尺寸
    void *rest[1];    //塊的參數(shù)簽名信息
}_testBlock_desc_DATA = {0, sizeof(Block_testBlock), "v12@?0i8"};


//這部分是block塊函數(shù)體的定義部分,可以看出block的代碼塊都轉化為了普通的函數(shù),并且函數(shù)會默認增加一個隱藏的__cself參數(shù),用來指向block對象本身。
static void testBlockfn(Block_testBlock *__cself, int c) {
    
    //還原函數(shù)體內引用外部的數(shù)據(jù)對象和變量。
    Block_b *b = __cself->b;
    Block_blockStr *blockStr = __cself->blockStr;
    int a = __cself->a;
    NSString *__strong strongStr = __cself->strongStr;
    NSString *__weak weakStr = __cself->weakStr;
    
    //int d = a + b + c;
    int d = a + b->forwarding->b + c;  //注意這里block變量使用方式。
    
    //NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
    NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr->forwarding->blockStr, weakStr);
    
}

void test()
{
    int a = 10;
    
    //__block int b = 20;
    Block_b b = {nil, &b, 0, sizeof(struct Block_b), 20};

    // __block NSString *blockStr = @"123";
    Block_blockStr blockStr = {nil, &blockStr, 33554432, sizeof(Block_blockStr), @"123"};
    
    NSString *strongStr = @"456";
    
   __weak NSString *weakStr = @"789";
   
    //每個在代碼中的block塊都會生成對應的OC block對象,這里面用構造函數(shù)初始化這個block對象。
    Block_testBlock testBlock(&testBlockfn, &_testBlock_desc_DATA, a, strongStr, weakStr, &b, &blockStr, 570425344);
    
    a = 20;   //這個不會影響到block塊內執(zhí)行時a的值。
    // b = 40; 這個賦值會影響到block塊內執(zhí)行時b的值。
    b.forwarding->b = 40;  //注意__block類型變量的值的更新方式。
    
    //執(zhí)行block塊其實就是執(zhí)行block對象里面的函數(shù)。
   //testBlock(30);
    testBlock.funcPtr(&testBlock, 30);
}

先看函數(shù)test內的實現(xiàn)部分,我們發(fā)現(xiàn)所有帶 __block修飾符的變量的定義由:

 __block int b = 20;
     __block NSString *blockStr = @"123"; 

變?yōu)榱耍?/p>

Block_b b = {nil, &b, 0, sizeof(struct Block_b), 20};
    Block_blockStr blockStr = {nil, &blockStr, 33554432, sizeof(Block_blockStr), @"123"};

也就是說所有定義為__block類型的變量,在編譯時都會變?yōu)橐粋€個block對象變量。在編譯時系統(tǒng)會為每個帶__block修飾的變量生成一個和OC類內存結構兼容的結構體:

//  每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是_block int b 的結構體定義:
struct Block_b {
    void *isa;
    Block_b *forwarding;  //指向真正的block對象變量。
    int flags;
    int size;  //結構體的size。
    int b;   //保存代碼中定義的變量。
};

//每個block變量都會生成一個和OC類內存結構兼容的結構體。下面是 __block NString *blockStr 的結構體定義:
struct Block_blockStr {
    void *isa;
    Block_blockStr *forwarding;  //指向真正的block對象變量。
    int flags;
    int size;
    NSString * blockStr;  //保存代碼中定義的變量。
};

上面的兩個結構體都有固定的格式,而且也和OC類的內存結構匹配。也就是說當定義__block修飾的變量時,系統(tǒng)會把他轉化為一個OC對象。 為什么要把__block定義的變量轉變?yōu)镺C對象呢?這個是和__block這個關鍵字所表達的意思是一致的,也就是定義為__block類型的變量是不會在block代碼塊內產(chǎn)生副本的,而是保持唯一性。每個block對象變量的isa都固定設置為nil; 而forwarding則是指向真正操作的block對象變量,如果某個block對象變量只是在一個棧block對象里面被使用則這時候forwarding是指向block對象變量自己,而如果這個block對象變量在一個堆block對象里面被使用則這時候forwarding則是指向一個堆block對象變量的地址。
再來看test函數(shù)中的block塊的定義部分。從代碼中可以發(fā)現(xiàn)原先在代碼中定義的block塊,被拆分為了block對象和全局函數(shù)兩部分來實現(xiàn)。因此可以看出在iOS內所有定義的block代碼塊系統(tǒng)在編譯時都會轉化為個OC對象(NSBlock類是用來描述block代碼塊的OC類,系統(tǒng)一共支持棧block:NSStackBlock,堆block:NSMallocBlock,全局block:NSGlobalBlock三種類型的block。具體的細節(jié)和差異不在本文展開,請大家自行查找相關的資料。)。因此在編譯時我們會為每個block代碼塊都生成一個和OC類兼容的結構體,在我們的例子里面的結構體定義如下:

//每個block塊都會生成一個和OC類兼容的結構體。
struct Block_testBlock {
    //前面5個數(shù)據(jù)成員在所有block定義中都相同,并且和OC兼容。
    Class isa;
    int flags;
    int reserved;
    void *funcPtr;   //block塊的全局函數(shù)的地址。
    Block_testBlock_Desc* desc;   //block的描述。

    //所有在block代碼塊引用的外部數(shù)據(jù)都會成為結構體的同名數(shù)據(jù)成員。
    int a;
    NSString * strongStr;
    NSString * __weak weakStr;
    Block_b *b;
    Block_blockStr *blockStr;

    //結構體的構造函數(shù)。
    Block_testBlock(void *_funcPtr, Block_testBlock_Desc *_desc, int _a, NSString * _strongStr, NSString * _weakStr, struct Block_b *_b, Block_blockStr *_blockStr, int _flags)
    {
        isa = &_NSConcreteStackBlock;  //根據(jù)具體的block類型賦值。
        flags = _flags;
        reserved = 0;
        funcPtr = _funcPtr;
        desc = _desc;
        a = _a;
        strongStr = _strongStr;
        weakStr = _weakStr;
        b = _b->forwarding;  //其實就是指向自己
        blockStr = _blockStr->forwarding;
    }
};

//每個block塊的描述信息結構體,主要是保存block的尺寸,以及block中函數(shù)的參數(shù)信息。
struct Block_testBlock_Desc {
    unsigned long reserved;
    unsigned long size; //塊的尺寸
    void *rest[1];    //塊的參數(shù)簽名信息
}_testBlock_desc_DATA = {0, sizeof(Block_testBlock), "v12@?0i8"};

可以看出我們定義的block代碼塊都會生成2個結構體:

Block_testBlock用來保存block的信息以及block內部要用到的所有數(shù)據(jù)。所有block對象結構體的前5個數(shù)據(jù)成員都是一致的,也就是和OC類的內存結構是兼容的。其中的isa用來保存block的類信息,這里面的類信息會根據(jù)block所處的位置的不同而不同。而后面的5個數(shù)據(jù)成員就是在block代碼塊內使用外部對象的副本。正是因為每個block對象在編譯時保存了代碼塊內使用代碼塊外的對象的副本,所以我們才能在后續(xù)代碼執(zhí)行時能夠訪問到這些信息。
Block_testBlock_Desc用來描述這個block的size以及block方法的參數(shù)的簽名信息。

下面就是Block_testBlock 實例的構造方法:

//每個在代碼中的block塊都會生成對應的OC  block對象,這里面構造函數(shù)初始化這個block對象。
    Block_testBlock testBlock(&testBlockfn, &_testBlock_desc_DATA, a, strongStr, weakStr, &b, &blockStr, 570425344);

上面可以看出,一旦在代碼中出現(xiàn)了block代碼塊,編譯時就會建立一個block對象,然后將block對象關聯(lián)的函數(shù)代碼地址、以及使用的外面的數(shù)據(jù)作為block對象的構造函數(shù)的參數(shù)來創(chuàng)建這個block對象。

最后我們再來考察block代碼的全局函數(shù)的實現(xiàn):

//這部分是block代碼函數(shù)體的定義部分,可以看見函數(shù)默認增加一個隱藏的__cself參數(shù)。
static void testBlockfn(Block_testBlock *__cself, int c) {

    //還原函數(shù)體內引用外面的變量。
    Block_b *b = __cself->b;
    Block_blockStr *blockStr = __cself->blockStr;
    int a = __cself->a;
    NSString *__strong strongStr = __cself->strongStr;
    NSString *__weak weakStr = __cself->weakStr;


    //int d = a + b + c;
    int d = a + (b->forwarding->b) + c;

    //NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
    NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr->forwarding->blockStr, weakStr);

}

上面的代碼片段中,可以看出block塊全局函數(shù)除了定義的int類型參數(shù)外,還增加了一個隱藏的參數(shù)__cself用來指向block對象。然后在函數(shù)體的開始位置把使用的外部數(shù)據(jù)的副本還原到函數(shù)的棧內。這也是為什么我們能在block代碼塊內用到外面的數(shù)據(jù)的原因了。這里我們需要進一步考察這幾個副本的意義:

對于基本類型a的副本來說就是完全的內存拷貝,因此在block代碼塊內更新這些數(shù)據(jù)是不會影響到外面,同時外面的更新也不會影響到里面了。
對于對象類型的strongStr和weakStr而言這個副本只是指針的拷貝而不是所指對象的拷貝,因此在block代碼塊內能夠讀取最新的屬性和設置新的屬性值。
對于__block類型的對象來說,你會發(fā)現(xiàn)他也是指針的拷貝,所以也不會產(chǎn)生多份內存副本,同時可以看出對__block類型數(shù)據(jù)的讀取和設置我們都是間接來完成的,因此這里代碼塊內更新數(shù)據(jù)能影響外面,同時外面的更新也能影響里面。

好了,所有我要介紹的內容就到這里了,上面就是iOS的block的內部實現(xiàn)機制。我相信通過我上面的介紹能夠讓你了解到了block在編譯時所做的事情,以及能夠了解到__block, __weak, __strong各種修飾符的意義和差別。
。

在iOS4出來后,蘋果公司在OC中推出了block機制(也許更早就有了)。并且在后續(xù)的版本中大量的推廣和使用了這項技術,比如對視圖動畫API的改版,比如GCD技術等等。block技術并不是什么新技術,他的本質就是閉包功能在iOS上的實現(xiàn)而已。而閉包功能在其他很多語言中都有實現(xiàn),比如JAVA中接口的匿名實現(xiàn)。用閉包可以解決那些執(zhí)行邏輯和上下文環(huán)境解耦的場景,如果從設計模式的角度來考慮的話閉包就是一種策略模式(Strategy)的實現(xiàn)。

作者:歐陽大哥2013
鏈接:http://www.itdecent.cn/p/595a1776ba3a

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容