Objective-C 之Block(2)

Block的實質

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:是一個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 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的結構實例中。

相關文章
相關標籤/搜索