你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> [IOS學習]之八、Blocks的實現

[IOS學習]之八、Blocks的實現

編輯:IOS開發綜合
我們先來轉換一個簡單的block代碼: 源:
int main() {
               void (^blk)(void) = ^{printf();};
               blk();
               return 0;
        }

struct __block_impl {
     void *isa;
     int Flags;
     int Reserved;
     void *FuncPtr;
};
struct __main_block_ipml_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();
}
static struct __main_block_desc_0 {
     unsigned long reserved;
     unsigned long Block_size;
} __main_block_desc_0_DATA = {
     0, 
     sizeof(struct __main_block_impl_0)
};
int main() {
     void (*blk)(void) = 
          (void (*)(void))&__main_block_impl_0(
               (void *)__main_block_func_0, &__main_block_desc_0_DATA);

     ((void (*)(struct __block_impl *))(
          (struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);

     return 0;
}

可以通過轉換後的源代碼看到,通過blocks使用的匿名函數,實際上被作為簡單的c語言函數來處理。 在轉換後的代碼的命名上,是根據block語法所屬的函數名和該block語法在該函數出現的順序值。 來看block的語法: ^{printf();}; 對應的是: static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf(); } 這裡的__cself相當於c++中的this
函數參數聲明:struct __main_block_impl_0 *__cself 與c++的this和oc的self相同,參數__cself 是 __main_block_impl_0結構體的指針。
struct __main_block_ipml_0 {
     struct __block_impl impl;
     struct __main_block_desc_0 *Desc;
}

這裡是取出構造函數的代碼, 其中impl是__block_impl結構體。
struct __block_impl {
     void *isa;
     int Flags;
     int Reserved;
     void *FuncPtr;
};
從名稱中可以看到這些標志,為今後升級所需的區域還有函數指針。 Desc是指針,是__main_block_desc_0結構體的。
static struct __main_block_desc_0 {
     unsigned long reserved;
     unsigned long Block_size;
};
這裡也是為今後升級所需區域和block的大小。
有了這些結構體,就應該有初始化:
__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;
     }
可以看到這裡有一個_NSConcreteStackBlock函數,他是用戶初始化__block_impl結構體的isa成員的。

main中構造函數是如何調用的: void (*blk)(void) = (void (*)(void))&__main_block_impl_0( (void *)__main_block_func_0, &__main_block_desc_0_DATA); 拆分: struct __main_block_impl_0 tmp = __main_block_func_0, &__main_block_desc_0_DATA); struct __main_block_impl_0 *blk = &tmp; 這樣就容易理解了。 該源代碼在棧上生成__main_block_impl_0結構體實例的指針。我們將棧上生成的__main_block_impl_0結構體實例的指針賦值給__main_block_impl_0結構體指針類型的變量blk;
注意到生成tmp的函數的參數, 第一個參數是由block語法轉換的c語言函數指針,第二個參數是作為靜態全局變量初始化的__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)是圖和根據這些參數進行初始化的:
struct __block_impl {
     void *isa;
     int Flags;
     int Reserved;
     void *FuncPtr;
};
該結構體根據構造函數: isa = &_NSConcreteStackBlock; Flags = 0; Reserved = 0; FuncPtr = __main_block_func_0; Desc = &__main_block_desc_0_DATA;
接下來看blk();
 ((void (*)(struct __block_impl *))(
          (struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
我們去掉轉換部分:
(*blk->impl.FuncPtr)(blk);
這是簡單的使用函數指針調用函數。 由Block語法轉換的__main_block_func_0函數的指針被賦值成員變量FuncPtr中, __main_block_func_0函數的參數__cself指向Block值, 在調用該函數的源代碼中可以看出Block正是作為參數進行了傳遞。
這裡的isa = &_NSConcreteStackBlock; 將Block指針賦給Block的結構體成員變量isa。 在這裡,我們需要理解oc類和對象的實質, 其實,所謂的block就是oc對象。 id 這個類型是用於存儲oc對象的,在源碼中,雖然可以像使用void*那樣使用id,但是id類型也能夠在c中聲明。 來看id:
struct objc_class {
     Class isa;
};
typedef struct objc_class *Class;
typedef struct objc_object {
     Class isa;
} *id;
id是objc_object結構體的指針類型。
我們來聲明一個oc類:
@interface MyObject :NSObject
{     
     int val0;
     int val1;
}
@end

這個類的對象結構體如下:
struct MyObject {
     Class class;
     int val0;
     int val1;
};

MyObject類的實例變量val0,val1被直接聲明為對象的結構體成員。 在oc中,由類生成對象,意味著 像該結構體這樣,生成由該類生成的對象的結構體實例。生成的各個對象,由該類生成的對象的各個結構體實例,通過成員變量isa保持該類的結構體實例指針。 如圖: \ \
各類的結構體就是基於objc_class結構體的class_t結構體,class_t結構體:
struct class_t {
     struct class_t *isa;
     struct class_t *superclass;
     Cache cache;
     IMP *vtable;
     uintptr_t data_NEVER_USE;
};

在oc中,如NSObject的class_t結構體實例以及NSMutablearray的class_t結構體實例等,均生成並保持各個類的class_t結構體實例。 該實例特有聲明的成員變量、方法的名稱、方法的實現(即函數指針)、屬性以及父類的指針,並被oc運行時庫所使用。
看剛剛的結構體:
struct __main_block_impl_0 {
     void *isa;
     int Flags;
     int Reserved;
     void *FuncPtr;
     struct __main_block_desc_0 *Desc;
};

他就相當於objc_object結構體的oc類對象的結構體。 isa = &NSConcreteStackBlock; 即_NSConcreteStackBlock相當於class_t結構體實例, 在將Block作為oc的對象處理時,關於該類的信息放置在:_NSConcreteStackBlock中。 最後Block即為OC對象。


截獲自動變量值 通過clang進行轉換的源代碼:
struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0 *Desc;
     const char *fmt;
     int val;

     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) {
          impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
     }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     const char &fmt = __cself->fmt;
     int val = __cself->val;
     printf(fmt, val);
}

static struct __main_block_desc_0 {
     unsigned long reserved;
     unsigned long Block_size;
} __main_block_desc_0_DATA = {
     0, 
     sizeof(struct __main_block_impl_0)
};

int main() {
     int dmy = 256;
     int val = 10;
     const char *fmt = "val = %d\n";
     void (*blk)(void) = &__main_block_impl_0(
          __main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
     return 0;
}

首先我們看到:Block語法表達式中,使用的自動變量被作為成員變量追加到了__main_block_impl_0中: int val。 如果Block語法表達式沒有使用的自動變量是不會追加的。 Blocks的自動變量截獲只針對Block中使用的自動變量。 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) 這一段時初始化變量。 在初始化結構體實例的時候,根據傳遞給構造函數的參數對由自動變量追加的成員變量進行初始化。 通過:void (*blk)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, fmt, val); 這裡使用執行Block語法時的自動變量fmt和val來初始化__main_block_impl_0結構體實例。 impl.isa = &_NSConcreteStackBlock; impl.Flags = 0; impl.FuncPtr = __main_block_func_0; Desc = &__main)block_desc_0_DATA; fmt = "val = %d\n"; val = 10;
這樣,我們知道 在__main_block_impl_0結構體實例中,自動變量值被截獲。
現在這裡是使用block的匿名函數的實現:^{printf(fmt, val);};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     const char &fmt = __cself->fmt;
     int val = __cself->val;
     printf(fmt, val);
}

這裡可以看到,截獲到__main_block_impl_0結構體實例的成員變量上的自動變量。 這些變量在Block語法表達式之前被聲明定義。 因此,原來的源代碼表達式無需改動便可使用截獲的自動變量值執行。
自動變量就是 在執行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構體實例中。
最後,Block不能直接使用c語言數組類型。看一下數組傳遞的源代碼: void func(char a[10]) { ---; } int main() { char a[10] = {1}; func(a); } 這裡 在構造函數中,將參數賦值給成員變量中,這樣在變換了block語法的函數內 可由成員變量賦值給自動變量: void func(char a[10]) { char b[10] = a; ---; } int main() { char a[10] = {1}; func(a); } 這樣是不能通過編譯的。


__block說明符
在剛剛的代碼:^{printf(fmt, val);};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     const char &fmt = __cself->fmt;
     int val = __cself->val;
     printf(fmt, val);
}

中,Block中所使用的被截獲自動變量就如:帶有自動變量值的匿名函數。 所說的,僅截獲自動變量的值。 Block中使用自動變量後,在Block的結構體實例中重寫該自動變量也不會改變原先截獲的自動變量。 如果在Block中視圖改變自動變量,將引起編譯錯誤。 但是這樣,我們就不能再block中保存值了。 解決方法: 1. c中有一個變量,允許block改寫值。 靜態變量 靜態全局變量 全局變量
在Block中,訪問靜態全局變量/全局變量沒有什麼改變,可以直接使用。 靜態變量中,轉換後的函數原本就設置在含有block語法的函數外,所以無法從變量作用域訪問。
int global_val = 1;
static int static_global_val = 2;
int main() {
     static int static_val = 3;
     void (^blk)(void) = ^{
          global_val *= 1;
          static_global_val *= 2;
          static_val *= 3;
     };
     return 0;
}
轉換後:
int global_val = 1;
static int static_global_val = 2;

struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     int *static_val;

     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags = 0):static_val(_static_val) {
          impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
     }
}

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     int *static_val = __cself->static_val;
     
     global_val *= 1;
     static_global_val *= 2;
     (*static_val) *= 3;
}

static struct __main_block_desc_0 {
     unsigned long reserved;
     unsigned long Block_size;
}     __main_block_desc_0_DATA = {
          0, 
          sizeof(struct __main_block_impl_0)
};

int main() {
     static int static_val = 3;
     blk = &__main_block_impl_0(
                    __main_block_func_0, &__main_block_desc_0_DATA, &static_val);
     return 0;
}

全局變量和靜態全局變量並沒有轉換,而靜態變量的指針對他進行了訪問。 將靜態變量static_val的指針傳遞給__main_block_impl_0結構體的構造函數,並保存。這是超出作用域後使用變量的最簡單的方法。
2、使用__block說明符 __block存儲域類說明符 __block storage-class-specifier c中的存儲域類說明符: typedef extern static auto register 這裡的__block說明符類似於static 、 auto和register說明符, 他們勇於指定變量值設置到那個存儲區域中。
__block int val = 10;
void (^blk)(void) = ^{val = 1;};

編譯後:
struct __Block_byref_val_0 {
     void *__isa;
     __Block_byref_val_0 *__forwarding;
     int __flags;
     int __size;
     int val;
};
struct __main_block_impl_0{
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     __Block_byref_val_0 *val;

     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
               __Block_byref_val_0 *_val, int flags = 0) : val(_val->__forwarding) {
          impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
     }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     __Block_byref_val_0 *val = __cself->val;
     (val->__forwarding->val) = 1;
}

static void __main_block_copy_0( struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) {
     __Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}

static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
     __Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}

static struct __main_block_desc_0 {
     unsigned long reserved;
     unsigned long Block_size;
     void (*copy) (struct __main_block_impl_0*, struct __main_block_impl_0*);
     void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
     0,
     sizeof(struct __main_block_impl_0),
     __main_block_copy_0,
     __main_block_dispose_0
};
int main() {
     __Block_byref_val_0 val = {
          0,
          &val,
          0,
          sizeof(__Block_byref_val_0),
          10
     };
     blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
     return 0;
}
代碼很長, 這個__block變量val是怎樣轉換過來的 ? __Block_byref_val_0 val = { 0, &val, 0, sizeof(__Block_byref_val_0), 10 }; 他變成了結構體。 __block變量也同Block一樣變成了__Block_byref_val_0結構體類型的自動變量, 即在棧上生成的__Block_byref_val_0結構體實例。 初始化為10, 這個值也出席那種結構體實例的初始化中: struct __Block_byref_val_0 { void *__isa; __Block_byref_val_0 *__forwarding; int __flags; int __size; int val; }; 這意味著 該結構體持有相當於原自動變量的成員變量。 上面結構體中的val相當於原來自動變量的成員變量。
來看給__block變量賦值的代碼: ^{val = 1;} 轉換後: static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; (val->__forwarding->val) = 1; } 在給靜態變量賦值的時候 使用了指針。 而向__block變量賦值要更加復雜。 Block的__main_block_impl_0結構體實例持有指向__block變量的__Block_byref_val_0結構體實例的指針。 在__Block_byref_val_0結構體實例的成語變量__forwarding持有指向該實例自身的指針。通過__forwarding訪問成員變量val。(這裡的val是該實例自身持有的變量,它相當於原自動變量) \

關於__forwarding會在續集繼續討論。
-----2014/3/18 Beijing
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved