Block是「帶有自動變量值的匿名函數」。數組
經過clang -rewirte-objc 源代碼文件名
就能將含有Block語法的源代碼變換爲cpp的源代碼。xcode
int main(int argc, char * argv[]) {
void (^blk)(void) = ^{printf("Block");};
blk();
return 0;
}
複製代碼
變換後截取其中的代碼邏輯部分以下:bash
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");};
複製代碼
變換後爲:ui
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值的變量。this
這個方法中參數的聲明爲:spa
struct __main_block_impl_0 *__cself
複製代碼
參數__cself是__main_block_impl_0結構體的指針。3d
該結構體聲明以下:指針
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;
}
};
複製代碼
去除構造函數的部分爲:code
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指針指向類。
每個類本質上都是一個對象,類實際上是元類(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 copy
int val = __cself->val; // bound by copy
printf(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 copy
int val = __cself->val; // bound by copy
printf(fmt,val);
}
複製代碼
在轉換後的代碼中,截獲到__main_block_impl_0結構體實例的成員變量上的自動變量,這些變量在Block語法表達式以前被聲明定義。所以,原來的源代碼表達式無需改動遍能夠使用截獲的自動變量值執行。
總的來講,所謂「截獲自動變量值」意味着在執行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構實例中。