1. 程式人生 > >《Objective-C高階程式設計》讀書筆記--2.3.1--Blocks的實質

《Objective-C高階程式設計》讀書筆記--2.3.1--Blocks的實質

前言

Blocks的原理,每當自己對知識體系有一定提升之後,再回過頭來看一下曾經讀過的書籍,會發現對它的理解逐步加深。藉著讀書筆記活動,立個小目標,把Block徹底搞明白,重讀《Objective-C高階程式設計 iOS與OS X多執行緒和記憶體管理》第二章節block原理部分,一方面給自己做個筆記,另一方面加深以下印象。

block實質

block程式碼:

void (^blk)(void) = ^ {
        printf("Block");
    };
    blk();
複製程式碼

執行xcrun -sdk iphonesimulator clang -rewrite-objc 原始碼檔名

就能將含有Block的程式碼轉換為C++的原始碼。我是按照書上的示例,同樣轉換的main.m檔案,轉換完之後這裡就會多出一個main.cpp的檔案,開啟很恐怖,六萬多行...

實際上和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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf
("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)}; int main(int argc, char * argv[]) { 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結構看上去有點抽象,不過理解起來並不難。

首先先從__main_block_func_0函式開始,因為我們想要執行的回撥看原始碼都是寫在這個函式裡面的,block使用的匿名函式(也就是我們定義的block)實際上被作為簡單的C語言函式(block__main_block_func_0)來處理,該函式的引數__cself相當於OC例項方法中指向物件自身的變數self,即__self為指向Block值的變數。__self與OC裡面的self相同也是一個結構體指標,是__main_block_impl_0結構體的指標,這個結構體宣告如下:

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,也是一個結構體,宣告如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
複製程式碼

先看FuncPrt,這個就是block括號中函式的函式指標,呼叫它就能執行block括號中的函式,實際上在呼叫block的時候就是呼叫的這個函式指標,執行它指向的具體函式實現。 第二個成員變數是Desc指標,以下為其__main_block_desc_0結構體宣告:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
複製程式碼

其結構為今後版本升級所需的區域和Block的大小。 實際上__main_block_impl_0結構體展開最後就是這樣:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  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;
  }
複製程式碼

這就是整個__main_block_impl_0結構體所包含的,既然定義了這個結構體的初始化函式,那在詳細看一下它的初始化過程,實際上該結構體會像下面這樣初始化:

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
複製程式碼

__main_block_func_0這不就是上面說到的那個指向函式實現的那個函式指標,也就是說只需要呼叫到結構體裡面的FuncPtr就能呼叫到我們的具體實現了。那這個建構函式在哪裡初始化的,看上面的原始碼是在我們定義block的時候:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
複製程式碼

簡化為:

struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
複製程式碼

該原始碼將__mian_block_impl_0結構體型別的自動變數,即棧上生成的__mian_block_impl_0結構體例項的指標,賦值給__mian_block_impl_0結構體指標型別的變數blk。聽起來有點繞,實際上就是我們最開始定義的blk__main_block_impl_0結構體指標指向了__main_block_impl_0結構體的例項。

接下來看看__main_block_impl_0結構體例項的構造引數:

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
複製程式碼

第一個引數為由Block語法轉換的C語言函式指標,第二個引數是作為靜態全域性變數初始化的__main_block_desc_0結構體例項指標,以下為__main_block_desc_0結構體例項的初始化部分程式碼:

static struct __main_block_desc_0 __main_block_desc_0_DATA = { 
    0, 
    sizeof(struct __main_block_impl_0)
};
複製程式碼

__main_block_impl_0結構體例項的大小。

接下來看看棧上的__main_block_impl_0結構體例項(即Block)是如何根據這些引數進行初始化的。也就是blk()的具體實現:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
複製程式碼

簡化以下:

(*blk->impl.FuncPtr)(blk);
複製程式碼

FuncPtr正是我們初始化__main_block_desc_0結構體例項時候傳進去的函式指標,這裡使用這個函式指標呼叫了這個函式,正如我們剛才所說的,有block語法轉換的__main_block_func_0函式的指標被賦值成員變數FuncPtr中。blk也是作為引數進行傳遞的,也就是最開始講到的__cself。到此block的初始化和呼叫過程就結束了。

待更新...