iOS Block实现探究

使用clang的rewrite-objc filename 可以将有block的c代码转换成cpp代码。从中可以看到block的实现。

#include <stdio.h>
int main()
{
  void (^blk)(void) = ^{
    printf("Block\n");
  };
  blk();
  return 0;
}

使用clang rewrite-objc以后会看到block的实现

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;
 }
};

可以看到其实block是一个正常的OC类

来看看block是怎样访问外部变量的

int main()
{
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (^blk)(void) = ^{
    printf(fmt, val);
  };
  return 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;
  }
};


int main()
{
  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);
  return 0;
}

block的变量会被复制进block中

如果当block要改变传入的变量值怎么办?首先看一下全局变量和本地静态变量

int global_val = 1;
static int static_global_val = 2;
int main()
{
  static int static_val = 3;
  void(^blk)(void) = ^{
  global_val *= 1;
   static_global_val *= 2;
  static_val *= 3;
  };
  blk();
  return 0;
}



struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}

由于全局变量是在Data Section中,所以直接可以访问。局部静态变量是通过将其指针传入到block中,block就可以对其值进行修改。

然后看一下__block修饰符变量

int main()
{
  __block int val = 10;
  void (^blk)(void) = ^{val = 1;};
  blk();
  return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,             __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)

block修饰符的变量,会生成一个Block_byref_val_0的struct,然后通过访问其forwarding来访问val值。因为Block有可能是在stack或者heap中,所以用forwarding来访问。之所以会将__block单独生成一个struct是因为可能该变量会被多个block使用。

Block分三种类型

0) NSConcreteStackBlock –stack

1) NSConcreteGlobalBlock –data area

2) NSConcreteMallocBlock –heap

自动copy block

当开启ARC时,在某些情况编译器会自动copy block,从stack到heap。

typedef int (^blk_t)(int);
blk_t func(int rate)
{
  return ^(int count){return rate * count;};
}


blk_t func(int rate)
{
  blk_t tmp = &__func_block_impl_0(
__func_block_func_0, &__func_block_desc_0_DATA, rate);

  tmp = objc_retainBlock(tmp);
  return objc_autoreleaseReturnValue(tmp);
}

有些情况,编译器是无法检测是否应该copy block:

当block作为参数传递到方法或函数中。

但是,如果该方法或函数在内部copy,就不用手动再copy:

0)cocoa framework方法, 有usingBlock

1) GCD API

- (id) getBlockArray
{
  int val = 10;
  return [[NSArray alloc] initWithObjects:
 [^{NSLog(@"blk0:%d", val);} copy],
 [^{NSLog(@"blk1:%d", val);} copy], nil];
}

__forwarding

当block从stack copy到 heap中时,block中用到的block也会copy到heap中,并且copy到heap中的block拥有该block。

__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
NSLog(@"%d", val);

在block和外的++val都会变成 ++(val.forwarding->val);
当block copy到heap中后, stack中的
forwarding会指向heap中的block, heap中的forwarding会指向自己的block值,这样保证了forwarding指向的是同一个变量值。

Block什么时候会copy到heap中

0)对block调用copy方法。

1)block作为一个函数的返回值。 编译器自动copy

2)赋值给id或block type class 有__strong 修饰符的成员变量。 编译器自动copy

3)usingBlock, GCD API。 在函数内copy

什么时候用该copy block?

0) block是函数返回值

1) block赋值给id或block type class 有__strong 修饰符的成员变量。

2)3)usingBlock, GCD API。 在函数内copy