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的结构实例中。

相关文章
相关标签/搜索