Objective-C 之Block(2)
Block的實質
Block是“帶有自動變量值的匿名函數”。
通過clang -rewirte-objc 源代碼文件名就能將含有Block語法的源代碼變換為cpp的源代碼。
int main(int argc, char * argv[]) {void (^blk)(void) = ^{printf("Block");};blk();return 0;} 復制代碼變換后截取其中的代碼邏輯部分如下:
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");}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;}復制代碼首先為:
^{printf("Block");}; 復制代碼變換后為:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block"); } 復制代碼通過Block使用的匿名函數實際上被作為簡單的C語言函數來處理。 另外,根據Blocl語法所屬的函數名(此處為main)和該Block語法在函數出現的順序值(此處為0)來給經clang變換的函數命名。 該函數的參數__cself相當于C++實例方法中志向實例自身的變量this,或者OC中的self,即參數__cself為志向Block值的變量。
這個方法中參數的聲明為:
struct __main_block_impl_0 *__cself 復制代碼參數__cself是__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;} };復制代碼去除構造函數的部分為:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc; };復制代碼再看__block_impl結構體的聲明:
struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; }; 復制代碼里面包含某些標志、今后版本升級所需的區域以及函數指針。 第二個成員變量是Desc指針,以下為__main_block_desc_0結構體的聲明。
static struct __main_block_desc_0 {size_t reserved;size_t Block_size; } 復制代碼其結構為今后版本升級所需要的區域和Block的大小。
再看__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;} 復制代碼以上就是初始化__main_block_impl_0結構體成員的源代碼。
再看看main函數中構造函數的調用如下:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 復制代碼去掉轉換部分來看如下:
__main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0,&__main_block_imp_0_DATA);struct __main_block_impl_0 *blk = &temp; 復制代碼該源代碼將__main_block_impl_0結構體的自動變量,即棧上生成的__main_block_impl_0結構體實例的指針,賦值給__main_block_impl_0結構體指針類型的變量blk。
以下這句代碼代表最初的打印代碼:
void (^blk)(void) = ^{printf("Block");}; 復制代碼將打印的Block塊賦給Block類型變量blk,相當于將__main_block_impl_0結構體的指針賦值給變量blk。打印的代碼塊就是__main_block_impl_0結構體類型的自動變量,即棧上生成的__main_block_impl_0的結構體實例。
再來看__main_block_impl_0實例的構造方法參數:
__main_block_impl_0(__main_block_func_0,&__main_block_imp_0_DATA); 復制代碼第一個參數是由Block語法轉換C語言函數指針。第二個參數是作為靜態全局變量初始化的__main_block_desc_0結構體實例指針。
下面是__main_block_desc_0結構體實例的初始化部分代碼。
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)}; 復制代碼由上部分代碼可以,該源代碼使用Block,即__main_block_impl_0結構體實例的大小,進行初始化。
再來看__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;} };struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; };復制代碼以上,__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;} };復制代碼那么調用打印代碼塊的部分應該為blk();
就可以變換為以下代碼:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);復制代碼去掉轉換部分后:
(*blk->impl.FuncPtr)(blk); 復制代碼這是簡單地使用函數指針調用函數。由打印代碼塊轉換的__main_block_func_0函數的指針被賦值到結構體的成員變量FuncPtr中,也說明了__main_block_func_0的參數__cself指向Block值。
但是impl.isa = &_NSConcreteStackBlock;,將Block指針賦值給Block結構體成員變量isa。
注:isa為何物?
引用簡書作者曲年_《Objective-C isa 指針 與 runtime 機制》_一文中解釋如下
在Objective-C中,任何類的定義都是對象。類和類的實例(對象)沒有任何本質上的區別。任何對象都有isa指針。
那么什么是類呢?在xcode中用快捷鍵Shift+Cmd+O 打開文件objc.h 能看到類的定義:
#if !OBJC_TYPES_DEFINED /// An opaque type that represents an Objective-C class. typedef struct objc_class *Class;/// Represents an instance of a class. struct objc_object {Class _Nonnull isa OBJC_ISA_AVAILABILITY; };/// A pointer to an instance of a class. typedef struct objc_object *id; #endif/// An opaque type that represents a method selector. typedef struct objc_selector *SEL; 復制代碼可以看出: Class是一個objc_class結構類型的指針,id是一個objc_object結構類型的指針。
objc_class結構體的定義如下:
struct objc_class {Class _Nonnull isa OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class _Nullable super_class OBJC2_UNAVAILABLE;const char * _Nonnull name OBJC2_UNAVAILABLE;long version OBJC2_UNAVAILABLE;long info OBJC2_UNAVAILABLE;long instance_size OBJC2_UNAVAILABLE;struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif} OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */復制代碼各參數含義如下:
- isa:是一個Class 類型的指針. 每個實例對象有個isa的指針,他指向對象的類,而Class里也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調用時,先會從本身查找類方法的實現,如果沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個封閉的內循環。
- super_class:父類,如果該類已經是最頂層的根類,那么它為NULL。
- version:類的版本信息,默認為0
- info:供運行期使用的一些位標識。
- instance_size:該類的實例變量大小
- ivars:成員變量的數組
每一個對象本質上都是一個類的實例。其中類定義了成員變量和成員方法的列表。對象通過對象的isa指針指向類。
每一個類本質上都是一個對象,類其實是元類(meteClass)的實例。元類定義了類方法的列表。類通過類的isa指針指向元類。
所有的元類最終繼承一個根元類,根元類isa指針指向本身,形成一個封閉的內循環。
objc_class結構體與objc_object結構體相同。但是,objc_object結構題是各個對象在實現中使用的最基本的結構體,objc_class是類在視線中使用的最基本的結構體。
例:
@interface MyObject:NSObject {int val0;int val1; } 復制代碼基于objc_object結構體,該類的對象的結構體如下:
struct MyObject{Class isa;int val0;int val1; }復制代碼MyObject類的實例變量val0和val1被直接聲明為對象的結構體成員。 OC中由各類生成對象意味著,像該結構體這樣“生成由該類生成的對象的結構體實例”。生成的各個對象,即由該類生成的對象的各個結構體實例,通過成員變量isa保持該類的結構體實例指針。
截獲自動變量值
int main(int argc, char * argv[]) {int dmy = 256;int val = 10;const char *fmt = "val = %d\n";void (^blk)(void)=^{printf(fmt,val);};val = 2;fmt = "These values were changed.val=%d\n";blk();return 0; } 復制代碼通過clang -rewrite-objc轉換后的代碼如下:
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;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; // bound by copyint val = __cself->val; // bound by copyprintf(fmt,val);}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[]) {int dmy = 256;int val = 10;const char *fmt = "val = %d\n";void (*blk)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));val = 2;fmt = "These values were changed.val=%d\n";((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;}復制代碼有區別的部分僅僅在于以下部分:將變量作為成員變量追加到了__main_block_impl_0 結構體中。
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;} };復制代碼__main_block_impl_0結構體內聲明的成員變量了行于自動變量類型完全相同。但是Block語法表達式中沒有使用的自動變量不會被追加。Blocks的自動變量截獲只針對Block中使用的自動變量。
在初始化結構體實例是,根據傳遞給構造函數的參數對由自動變量追加的成員變量進行初始化。
__main_block_impl_0初始化的代碼過程總結來說就是以下賦值過程:
impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = &__main_block_desc_0_DATA; val = 10; fmt = "val = %d\n";復制代碼由此可知,在__main_block_impl_0結構體實例中(即打印代碼塊),自動變量被截獲。
再來看其中匿名函數代碼塊:
^{printf(fmt,val); };復制代碼可以轉換為:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt = __cself->fmt; // bound by copyint val = __cself->val; // bound by copyprintf(fmt,val);} 復制代碼在轉換后的代碼中,截獲到__main_block_impl_0結構體實例的成員變量上的自動變量,這些變量在Block語法表達式之前被聲明定義。因此,原來的源代碼表達式無需改動遍可以使用截獲的自動變量值執行。
總的來說,所謂“截獲自動變量值”意味著在執行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構實例中。
總結
以上是生活随笔為你收集整理的Objective-C 之Block(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分组加密的四种模式
- 下一篇: 开发:随笔记录之 Json字符串和对象的