Block那些事

Block底层实现以及__Block原理

Posted by Ted on November 29, 2017

查看LLVM官方文档Block-ABI-Apple

一、不含变量Block

void (^blk)(void) = ^{
    printf("hello world");
};

int main(int argc, const char * argv[]) {
    blk();
    return 0;
}

通过Clang命令,可以看到编译的C++文件:

clang -rewrite-objc main.m
struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("hello world");
    }

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

__main_block_func_0这个是block的执行函数,参数是__main_block_impl_0类型。

__main_block_impl_0这个结构体就是Block的底层实现了

  • isa:指向的是_NSConcreteGlobalBlock,这个是全局block
  • Flags:标志
  • FuncPtr:实现函数指针
  • Desc:block的一些信息

上面的情况是将block的定义在函数外,在函数内引用,特别的,当把block的定义放入函数体内,如下

int main(int argc, const char * argv[]) {
    void (^blk)(void) = ^{
        printf("hello world dk");
    };
    NSLog(@"%@", blk);
    return 0;
}

clang命令后:

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

isa指针由_NSConcreteGlobalBlock变为_NSConcreteStackBlock但根据LLVM实际打印结果

lockDemo[59807:10443592] <__NSGlobalBlock__: 0x100001040>

所以应当还是GlobalBlock,这里clang与LLVM有点不同。以LLVM为准。

二、含变量的Block

加了一个变量int i;

int main(int argc, const char * argv[]) {
    int i = 100;
    void (^blk)(void) = ^{
        printf("hello world %d",i);
    };
  	NSLog(@"%@",blk);
    blk();
    return 0;
}

打印结果

// MRC
BlockDemo[59851:10455160] <__NSStackBlock__: 0x7fff5fbff6a8>
// ARC
BlockDemo[60278:10523410] <__NSMallocBlock__: 0x10055e4b0>

Clang之后变成

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy

        printf("hello world %d",i);
    }

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, const char * argv[]) {
    int i = 100;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

通过对比可发现,isa指针已经指向NSConcreteStackBlock,Block被复制到了堆上,而且函数多个几个i

img

_main_block_impl_0结构体中多了一个i,成功将i变量的值捕获。因为main函数中的局部变量i和函数__main_block_func_0不在同一个作用域中,调用过程中只是进行了值传递。main函数的i是在栈中,block是在堆上,所以不能对值进行修改。

如果想要在block内修改i的值,会报错

img

三、Block内修改外部变量

我们都知道,在block内部修改变量的话,要在变量前面加上修饰符’__block’;

int main(int argc, const char * argv[]) {
    int i = 100;
    __block int block_i = i;
    void (^blk)(void) = ^{
        block_i = 50;
    };
  	NSLog(@"%@",blk);
    blk();
    return 0;
}

打印结果

// MRC
BlockDemo[59851:10455160] <__NSStackBlock__: 0x7fff5fbff6a8>
// ARC
BlockDemo[60278:10523410] <__NSMallocBlock__: 0x10055e4b0>

Clang之后变成

struct __Block_byref_block_i_0 {
  void *__isa;
__Block_byref_block_i_0 *__forwarding;
 int __flags;
 int __size;
 int block_i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_block_i_0 *block_i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_block_i_0 *_block_i, int flags=0) : block_i(_block_i->__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_block_i_0 *block_i = __cself->block_i; // bound by ref

        (block_i->__forwarding->block_i) = 50;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_i, (void*)src->block_i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    int i = 100;
    __attribute__((__blocks__(byref))) __Block_byref_block_i_0 block_i = {(void*)0,(__Block_byref_block_i_0 *)&block_i, 0, sizeof(__Block_byref_block_i_0), i};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_block_i_0 *)&block_i, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

可与发现,大概多了三个部分

img

当block从栈上被copy到堆上时,会调用__main_block_copy_0将__block类型的成员变量i从栈上复制到堆上,同时,栈上的__Block_byref_i_0结构体中的__forwarding指针也会指向堆上的地址。而当block被释放时,相应地会调用_main_block_dispose_0来释__block类型的成员变量i。

这样,就成功地修改变量的值。

修改外部变量

引用外部变量,值传递,只能值进行读操作,不能写

img

修改外部变量,在变量前加__block可以进行写操作

img

ARC与MRC

因此在ARC情况下,创建的block仍然是NSConcreteStackBlock类型,只不过当block被引用或返回时,ARC帮助我们完成了copy和内存管理的工作变成NSConcreteMallocBlock放在堆上。

在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。