iOS-Block的實現(一)

block 顧名思義就是代碼塊,將同一邏輯的代碼放在一個塊,使代碼更簡潔緊湊,易於閱讀,而且它比函數使用更方便,代碼更美觀,因而廣受開發者歡迎。但同時 block 也是 iOS 開發中坑最多的地方之一,因此有必要了解下 block 的實現原理,知其然,更知其所以然,才能從根本上避免挖坑和踩坑。

需要知道的是,block 只是 Objective-C 對閉包的實現,並不是 iOS 獨有的概念,在 C++、Java 等語言也有實現閉包,名稱不同而已。

特別聲明

以下研究所用的過程代碼由 clang 編譯前端生成,僅作理解之用。實際上 clang 根本不會將 block 轉換成人類可讀的代碼,它對 block 到底做了什麼,誰也不知道。

所以,切勿將過程代碼當做block的實際實現,切記切記!!!

將下面的 test.m 代碼用 clang 工具翻譯 test.cpp 代碼

clang -rewrite-objc test.m

test.m 代碼

/************* Objective-C 源碼 *************/
int main()
{
    void (^blk)(void) = ^{ printf("Block\n"); }; 
    blk();
    return 0;
}

test.cpp

/************* 使用 clang 翻譯後如下 *************/
struct __block_impl
{
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
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;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}
static struct __main_block_desc_0
{
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
int main()
{
    void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

接着,我們逐一來看下這些函數和結構體

block 結構體信息詳解

struct __block_impl

// __block_impl 是 block 實現的結構體
struct __block_impl
{
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
  • isa
    指向實例對象,表明 block 本身也是一個 Objective-C 對象。block 的三種類型:_NSConcreteStackBlock_NSConcreteGlobalBlock_NSConcreteMallocBlock,即當代碼執行時,isa 有三種值

    impl.isa = &_NSConcreteStackBlock;
    impl.isa = &_NSConcreteMallocBlock;
    impl.isa = &_NSConcreteGlobalBlock;

    從這些實例對象可以看出 block 所在內存區域分別爲stackRODataheap,後面文章會詳說。

  • Flags
    按位承載 block 的附加信息;

  • Reserved
    保留變量;

  • FuncPtr
    函數指針,指向 Block 要執行的函數,即{ printf(“Block\n”) };

struct __main_block_impl_0

// __main_block_impl_0 是 block 實現的結構體,也是 block 實現的入口
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;
    }
};
  • impl
    block 實現的結構體變量,該結構體前面已說明;

  • Desc
    描述 block 的結構體變量;

  • __main_block_impl_0
    結構體的構造函數,初始化結構體變量 implDesc

static void __main_block_func_0

// __main_block_func_0 是 block 要最終要執行的函數代碼
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}
static struct __main_block_desc_0

// __main_block_desc_0 是 block 的描述信息結構體
static struct __main_block_desc_0
{
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
  • reserved
    結構體信息保留字段

  • Block_size
    結構體大小

此處已定義了一個該結構體類型的變量 __main_block_desc_0_DATA

block 實現的執行流程

這裏寫圖片描述
最基礎的 block 實現就這麼簡單。


接着再看 block 獲取外部變量

block 獲取外部變量

運行下面的代碼

int main()
{
    int intValue = 1;
    void (^blk)(void) = ^{ printf("intValue = %d\n", intValue); };
    blk();
    return 0;
}

打印結果

intValue = 1

和第一段源碼不同的是,這裏多了個局部變量intValue,而且還在 block 裏面獲取到了。

通過前一段對 block 源碼的學習,我們已經瞭解到 block 的函數定義在 main() 函數之外,那它又是如何獲取 main() 裏面的局部變量呢?爲了解開疑惑,我們再次用 clang 重寫這段代碼

struct __block_impl
{
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
struct __main_block_impl_0
{
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int intValue;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _intValue, int flags=0) : intValue(_intValue)
    {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    int intValue = __cself->intValue; // bound by copy
    printf("intValue = %d\n", intValue);
}
static struct __main_block_desc_0
{
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
    int intValue = 1;
    void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, intValue);
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

原來 block 通過參數值傳遞獲取到 intValue 變量,通過函數

__main_block_impl_0 (void *fp, struct __main_block_desc_0 *desc, int _intValue, int flags=0) : intValue(_intValue)

保存到 __main_block_impl_0 結構體的同名變量intValue,通過代碼int intValue = __cself->intValue; 取出 intValue,打印出來。

構造函數 __main_block_impl_0 冒號後的表達式 intValue(_intValue) 的意思是,用 _intValue 初始化結構體成員變量 intValue。

有四種情況下應該使用初始化表達式來初始化成員:
1:初始化const成員
2:初始化引用成員
3:當調用基類的構造函數,而它擁有一組參數時
4:當調用成員類的構造函數,而它擁有一組參數時

參考:C++類成員冒號初始化以及構造函數內賦值

至此,我們已經瞭解了block 的實現,以及獲取外部變量的原理。但是,我們還不能在 block 內修改 intValue 變量。如果你有心試下,在 block 內部修改 intValue 的值,會報編譯錯誤

Variable is not assignable(missing __block type specifier)

那麼如何在 block 內修改外部變量呢,請看下篇: block 和變量的內存管理(二)

原文地址: https://www.zybuluo.com/MicroCai/note/51116

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章