[讀書筆記]iOS與OS X多線程和內存管理 [Blocks部分-2]

2.3 Blocks的實現

2.3.1 Block的實質
通過命令”clang -rewrite-objc 文件名”能夠將含有Block語法的源代碼轉換爲C++源代碼。
含有Block的源代碼如下:
#include<stdio.h>//不導入庫文件無法運行
intmain() {
   
void(^testBlock)(void)=^{
       
printf("i am testBlock");
    };
    testBlock();
}
轉換後的代碼有五百行左右,這裏只選擇我們關心的部分:
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;
  }
};
staticvoid__main_block_func_0(struct__main_block_impl_0 *__cself) {
        printf("i am testBlock");
}

staticstruct__main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = {
0,sizeof(struct__main_block_impl_0)};
intmain() {
   
void(*testBlock)(void)=(void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA);
    ((
void(*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
}

通過Blocks使用的匿名函數被作爲簡單的C語言函數來處理:
staticvoid__main_block_func_0(struct__main_block_impl_0 *__cself) {
        printf("i am testBlock");
}
該函數的參數”__cself”相當於C++實例方法中指向實例真身的變量this,也相當於OC示例方法中指向自身的變量”self”,即參數”__cself”爲指向Block值的變量。

__cself是__main_block_impl_0類型的結構體指針,該結構體的定義是(不包括構造函數):
struct__main_block_impl_0 {
 
struct __block_impl impl;
  struct __main_block_desc_0* Desc;
}
可以看到,__main_block_impl_0共有兩個變量,第一個是__block_impl類型的“impl”,定義如下
struct__block_impl {
 
void *isa;
 
int Flags;
 
int Reserved;
 
void *FuncPtr;
};
第二個是__main_block_desc_0類型的”Desc",定義如下:
staticstruct__main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
回頭看一下__main_block_impl_0的構造函數:
__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;
}
構造函數的調用是如下語句:
   void(*testBlock)(void)=(void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA);
去掉轉換部分,具體如下:
struct __main_block_impl_0 tmp=__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 *testBlock=&tmp;
其中調用構造函數時有兩個參數,第一個是由Block語法轉換的C語言函數指針,第二個作爲靜態全局變量初始化的__main_block_desc_0結構體實例指針,初始化代碼如下:
__main_block_desc_0_DATA = {0,sizeof(struct__main_block_impl_0)};
可知該源碼使用__main_desc_impl_0結構體實例的大小進行初始化。
下面看一下__main_desc_impl_0初始化的過程,展開__block_impl,其結構體與初始化對比如下:

結構體:
struct__main_block_impl_0 {
//__block_impl
 void*isa;
 
int Flags;
 
int Reserved;
  void *FuncPtr;
//
  struct __main_block_desc_0* Desc;
};
初始化:

//
    isa = &_NSConcreteStackBlock;
    Flags = 0;
    Reserved=0
    FuncPtr =__main_block_func_0;
//
    Desc =&__main_block_desc_0_DATA;

使用Block的源碼爲:testBlock();
轉換後的源碼爲:
((void(*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
去掉轉換部分之後:
(*testBlock->impl.FuncPtr)(testBlock);
這是使用函數指針調用函數。之前__main_block_func_0賦值給FuncPtr,而參數(__cself)就是指向Block的值。
文字的解釋不夠直觀,可看圖理解。


接下來解釋一下
isa = &_NSConcreteStackBlock;這句話。
將Block指針賦值給Block結構體成員變量isa。爲了理解這句話,先要理解OC中類與對象的實質,下面是解釋類與對象的關係.

Block是OC對象,“id”用來存儲OC對象,id類型也能夠在c語言中聲明:
typedef struct objc_object{
Class isa;
} *id;
id 爲objc_object結構體指針類型。Class的定義如下:
typedef struct objc_class*Class;
Class爲objc_class結構體的指針類型,objc_class結構體在模擬器目錄下/usr/include/objc/runtime.h(導入#import<objc/runtime.h>點擊可進入)中聲明如下(除去無關宏定義):
structobjc_class {
    Class isa;
};
objc_class與結構體objc_object的定義相同。但是分別是在類與對象中使用的結構體,下面通過簡單的OC類聲明來驗證一下。
@interfaceMyObject : NSObject
{
   
int val0;
   
int val1;
}
@end
基於objc_object結構體,該類的對象的結構體如下:
structMyObject{
    Class isa;
   
int val0;
   
int val1;
};
類中的實例變量被包含在對象的結構體中,類生成對象意味着類生成該類的對象的各個結構體實例,通過成員變量isa保持該類的結構體實例指針(即isa爲指向所屬類的結構體實例的指針),如下圖:



各類的結構體就是基於objc_class結構體的class_t結構體,class_t結構體在objc4運行時庫的聲明如下:
structclass_t{
   
struct class_t *isa;
   
struct class_t *superclass;
    Cache cache;
   
IMP *vtable;
    uintptr_t data_NEVER_USE;
};
在OC中,各個類的結構體實例均生成並保持各個類的class_t結構體實例。該實例存有類的相關信息,包括持有聲明的成員變量、方法的名稱、方法的實現(函數指針)、屬性即父類的指針,並被OC運行時庫所使用。

回到剛纔的代碼,_NSConcreteStackBlock相當於class_t的結構體實例,將Block作爲OC對象處理,關於該類的信息放置於_NSConcreteStackBlock中。所以說Block是OC對象。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章