Block 4:Block实质解析

http://m.blog.csdn.net/blog/womendeaiwoming/23885507

[原]Block 4:Block实质解析

2014-4-16阅读304 评论0

     Block是“带有局部变量值的匿名函数”,但Block究竟是什么呢?

     前面所说的Block语法看上去好像很特别,但它实际上是作为极普通的C语言源代码来处理的。通过支持Block的编译器,含有Block语句的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言源代码被编译。

     这不过是概念上的问题,在实际编译时无法转换成我们能够理解的源代码,但clang(LLVM编译器)具有转换为我们可读源代码的功能。通过“-rewrite-objc”选项就能将含有Block语法的源代码转换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。

clang - rewrite-objc 源代码文件名

     下面,我们转换Block语句。

int main(int argc, const char * argv[])
{
  void (^my_blk)(void) = ^{printf("Hello Block\n");};
  my_blk();
  return 0;
}

     此源代码的Block语句很简单,它省略了返回值类型以及参数列表。该源代码通过clang可转换为以下形式:

void *_NSConcreteStackBlock[32];

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("Hello Block\n");
}

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 argc, const char * argv[])
{
  void (*my_blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
  ((void (*)(__block_impl *))((__block_impl *)my_blk)->FuncPtr)((__block_impl *)my_blk);
  return 0;
}
     呃,6行源代码竟然增加到40来行。但是如果仔细观察就能发现,这段源代码虽长却并不是很复杂。下面,我们将源代码分成几个部分逐步理解。首先来看最被源代码中的Block语句:

^{printf("Hello Block\n");};

     可以看到,转换后的源代码中也含有相同的表达式:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  printf("Hello Block\n");
}
     如转换后的源代码所示,通过Blocks使用的匿名函数实际上被作为简单的C语言函数来处理。另外,根据Block语句所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang转换的的函数全名。

     该函数的参数__cself相当于C++实例方法中指向实例自身的变量this,或是Objective-C实例方法中指向对象自身的变量self,即参数__csef为指向Block值的变量。

    关于self请看这里。

     遗憾的是,由这次Block语法变换而来的__main_block_func_0函数并没有使用__cself。我们先来看看该参数的声明。

struct __main_block_impl_0 *__cself

     与C++的this和Objective-C的self相同,参数__cself是__main_block_impl_0结构体的指针。该结构体声明如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
}

     由于转换后的源代码中,也一并写入了其构造函数,所以看起来稍显复杂,如果除去该构造函数,__main_block_impl_0结构体会变得非常简单。第一个成员变量是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结构体的构造函数。

 __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结构体成员的源代码。我们刚刚跳过了_NSConcreteStackBlock的说明。_NSConcreteStatckBlock用于初始化__block_impl结构体的isa成员。虽然大家很想了解它,但在进行讲解之前,我们先来看看该构造函数的调用。

void (*my_blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

     因为转换较多,看起来不是很清楚,所以我们去掉转换的部分,具体如下 :

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *my_blk = &tmp;
     这样就容易理解了。该源代码将__main_block_impl_0结构体类型的自动类量,即栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构本指针类型的变量my_blk。以下为这部分代码对应的最初源代码。

  void (^my_blk)(void) = ^{printf("Hello Block\n”);};
     将Block语法生成的Block赋给Block类型变量my_blk。它等同于将__main_block_impl_0结构体实例的指针赋给变量my_blk。该源代码中的Block就是__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例。

     下面就来看看__main_block_impl_0结构体实例构造参数。

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
     第一个参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。以下为__main_block_desc_0结构体实例的初始化部分代码。

static struct __main_block_desc_0 __main_block_desc_0_DATA = {
  0,
  sizeof(struct __main_block_impl_0)
};
     由此可知,该源代码使用Block,即__main_block_impl_0结构体实例的大小,进行初始化。

     下面看看栈上的__main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。如果展开__main_block_impl_0结构体的__block_impl结构体,可记述为如下形式:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  struct __main_block_desc_0* Desc;
}
     该结构体根据构造函数会像下面这样进行初始化。

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

     虽然大家非常白切地想了解_NSConcreteStackBlock,不过我们还是先把其他部分讲完再对此进行说明。将__main_block_func_0函数指针赋给成员变量FuncPtr。

     我们来确认一下使用该Block的部分。

my_blk();

     这部分可转换为以下源代码:

((void (*)(__block_impl *))((__block_impl *)my_blk)->FuncPtr)((__block_impl *)my_blk);

     去掉转换部分:

(*my_blk->impl.FuncPtr)(blk);

     这就是简单地使用函数指针调用函数。正如我们刚才所确认的,由Block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中。另外也说明了,__main_block_func_0函数的参数__csefl指向Block值。在调用该函数的源代码中可以看出Block正是作为参数进行了传递。

     到此总算摸清了Block的实质,不过刚才路过没有这次是真的_NSConcreteStackBlock到底是什么呢?

isa = &_NSConcreteStackBlock;

     将Block指针赋给Block的结构体成员变量isa。为了理解它,首先要理解Objective-C类和对象的实质。其实,所谓Block就是Objective-C对象。

“id”这一变量类型用于存储Objective-C对象。在Objective-C源代码中,虽然可以像使用void *类型那样随意使用id,但此id类型也能够在C语言中声明。在/usr/include/objc/objc.h中是如下进行声明的(已去掉无关内容):

struct objc_object {
    Class isa;
};
typedef struct objc_object *id;

     id为objc_object结构的指针类型。我们再来看看Class。

typedef struct objc_class *Class;

     Class为objc_class结构体的指针类型。objc_class结构体在/usr/include/objc/runtime.h中声明如下(已去掉无关内容):

struct objc_class {
    Class isa;
} 

     这与objc_object结构体相同。然而,objc_object结构体和objc_class结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。下面我们通过编写简单的Objective-C类声明来确认一下:

@interface MyObject : NSObject {
  int val0;
  int val1;
}
@end
     基于objc_object结构体,该类的对象的结构体如下(已简化):

struct MyObject {
  Class isa;
  int val0;
  int val1;
};

     MyObject类的实例变量val0和val1被直接声明为对象的结构成员。“Objective-C中由类生成对象”意味着,像该结构体这样“生成由该类生成的对象的结构体实例”。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。

     各类的结构体就是基于objc_class结构体的class_t结构体。class_t结构体在objc4运行时库的runtime/objc-runtime-new.h中声明如下:

struct class_t {
  struct class_t *isa;
  struct class_t *superclass;
  Cache cache;
  IMP *vtable;
  uintptr_t data_NEVER_USE;
};

     在Objective-C中,比如NSObject的class_t结构体实例以及NSMutableArray的class_t结构体实例等,均生成并保持各个类的class_t结构体实例。该实例持有声明的成员变量、方法的名称、方法的实现(即函数指针)、属性以及父类的指针,并被Objective-C运行时库所使用。

到这里,就可以理解Objective-C的类与对象的实质了。

     那么回到刚才的Block结构体:

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  struct __main_block_desc_0* Desc;
};

     此__main_block_impl_0结构体相当于基于objc_object体的Objective-C类对象的结构体。另外,对其中的成员变量isa进行初始化,具体如下:

isa = &_NSConcreteStackBlock;

     即_NSConcreteStackBlock相当class_t结构体实例。在将Block作为Objective-C的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。

     现在大家就能理解Block的实质,知道Block即为Objective-C的对象了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值