Block源码分析

2017-12-26  本文已影响21人  alvin_wang
1.什么是Block

Block是带有自动变量(局部变量)的匿名函数。Block的内部数据结构如下:

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
2.block类型

C/C++/OC编译的程序占用内存分布的结构如下:

image.png

block按照不同的内存区域可以分为:

int globalNum = 3;
int main(int argc, const char * argv[]) {
    int a = 3;
    NSLog(@"this block is: %@",^{NSLog(@"I use nothing");});
    NSLog(@"this block is: %@",[^{NSLog(@"I use ivar %d",a);} copy]);
    NSLog(@"this block is: %@",^{NSLog(@"I use ivar %d",a);});
    NSLog(@"this block is: %@",^{NSLog(@"%d",globalNum);});
    return 0;
}

其输出结果如下:

2017-12-26 09:01:27.668046+0800 BlockDemo[98660:1165488] this block is: <__NSGlobalBlock__: 0x100001050>
2017-12-26 09:01:27.668246+0800 BlockDemo[98660:1165488] this block is: <__NSMallocBlock__: 0x100507f80>
2017-12-26 09:01:27.668293+0800 BlockDemo[98660:1165488] this block is: <__NSStackBlock__: 0x7fff5fbfefe0>
2017-12-26 09:01:27.668367+0800 BlockDemo[98660:1165488] this block is: <__NSGlobalBlock__: 0x1000010d0>

注意:在ARC的作用下,block类型通过=进行传递时,会导致objc_retainBlock->_Block_copy->_Block_copy_internal方法链,导致__NSStackBlock__类型的block转换为__NSMallocBlock__类型。

3. block的实现原理

用clang工具重写为c++代码来探究一下block的内部实现。
测试代码如下:

typedef int(^blk)(int);

int globalNum = 3;
int main(int argc, const char * argv[]) {
    int a = 3;
    __block int blockVar = 3;
    static int staticNum = 3;
    blk blk = ^(int count) {
        NSLog(@"a = %d", a);
        staticNum --;
        globalNum --;
        blockVar --;
        return count * a * globalNum * staticNum;
    };
    a = 10;
    NSLog(@"%d", blk(1));
    return 0;
}

clang重写c++后的代码如下:

typedef int(*blk)(int);

int globalNum = 3;
struct __Block_byref_blockVar_0 {
  void *__isa;
  __Block_byref_blockVar_0 *__forwarding;
 int __flags;
 int __size;
 int blockVar;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *staticNum;
  __Block_byref_blockVar_0 *blockVar; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_staticNum, __Block_byref_blockVar_0 *_blockVar, int flags=0) : a(_a), staticNum(_staticNum), blockVar(_blockVar->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int count) {
  __Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref
  int a = __cself->a; // bound by copy
  int *staticNum = __cself->staticNum; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_03_v9qmb8t568sgwcjv8bj5ng380000gn_T_main_6b603d_mi_0, a);
    (*staticNum) --;
    globalNum --;
    (blockVar->__forwarding->blockVar) --;
    return count * a * globalNum * (*staticNum);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->blockVar, 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 a = 3;
    __attribute__((__blocks__(byref))) __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 3};
    static int staticNum = 3;
    blk blk = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &staticNum, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
    a = 10;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_03_v9qmb8t568sgwcjv8bj5ng380000gn_T_main_6b603d_mi_1, ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 1));
    return 0;
}

我们看到block其实就是struct __main_block_impl_0 结构体,它由4个成员变量和一个构造函数组成。
其中第一个成员变量是__block_impl结构体,指向的是__main_block_func_0函数。
第二个成员变量是__main_block_desc_0结构体,负责管理block的内存管理。__main_block_copy_0__main_block_dispose_0就是利用OC的runtime进行内存管理。在Block中使用 __strong或者__block修饰符的id类型或对象类型的自动变量时,当block从栈复制到堆时,当对象需要retain的时候调用__main_block_copy方法增加引用计数,当需要释放的时候则调用__main_block_dispose方法释放对象。
第三个是int变量。因为block内部引用了外部的自动变量,所以在block结构体中多了一个同类型的成员变量。
第四个是捕获到的静态变量。
第五个是__block修饰符的变量。具体介绍看下一节。

__main_block_func_0包含了block的内部代码。其中包含了一个参数__cself,指向__main_block_impl_0,及匿名block自身。这种写法类似与OC中的方法消息传递。
另外可以看到系统加的注释bound by copy,自动变量通过__cself->val方式捕获。局部变量传入的是值,静态变量传入的是地址。因此局部变量无法更改,而静态变量可以更改。同时全局变量由于作用域大,因此不需要传入就可以自由的在block内部更改。而__block修饰符的变量是通过bound by ref方式被捕获进来的,具体介绍看下一节。

image.png
4. __block 修饰符

我们直到,当想要修改block外面的局部变量时,需要用__block修饰符。那么__block的原理是如何的呢。
从上面的转换代码中可以看到,带有__block修饰符的局部变量被转换为一个结构体__Block_byref_blockVar_0。这个结构体有5个成员变量。

struct __Block_byref_blockVar_0 {
  void *__isa;
  __Block_byref_blockVar_0 *__forwarding;
  int __flags;
  int __size;
  int blockVar;
};

从代码中我们可以看到blockVar->__forwarding->blockVar这样的方式来对__block修饰的变量进行操作,但是为什么要搞这么复杂呢?
__forwarding 指针就是针对堆里的block。把原来指向自己的__forwarding指针指向堆上的__block变量。然后堆上的变量的__forwarding指向自己。

image.png image.png
5. Block中的循环引用

循环引用,就是相互引用。比如A强引用B,B又强引用A。那么A和B的引用计数永远无法为0,造成内存泄漏。当然这种循环也可以是多个对象间的。


image.png

而对于block的循环引用,一般是一个对象引用block,而block内部又引用了这个对象。
打破这种循环引用一般有两种方法:1.弱引用;2.主动释放


image.png
5.1 弱引用
__weak typeof(self) weakSelf = self;
self.blk = ^{
    NSLog(@"%@",weakSelf);
};
5.2 主动释放
__block id blockSelf = self;
self.blk = ^{
    NSLog(@"%@",blockSelf);
    blockSelf = nil;
};
self.blk();

当然,弱引用和主动释放各有缺点:
主动释放的缺点:

弱引用的缺点:

当然对于弱引用,可以利用weak-strong-dance来保证不会出现因为self释放引起问题。

__weak typeof(self) weakSelf = self;
self.blk = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    NSLog(@"%@",strongSelf);
};
6. 参考链接

1.深入研究Block捕获外部变量和__block实现原理
2.老司机出品——源码解析之从Block说开去
3.A look inside blocks: Episode 1
4.A look inside blocks: Episode 2

上一篇下一篇

猜你喜欢

热点阅读