[iOS] block分析

2021-01-25  本文已影响0人  沉江小鱼

1. block 的类型

我们通过下面的代码,来分析一下block 的类型:

static int n = 99;
static NSString *name = @"test";

- (void)testBlockType{
    // 0.没有引用外部变量的 block0
    void(^block0)(void) = ^{
        NSLog(@"block0");
    };
    NSLog(@"block0 => %@",block0);
    
    // 1.引用静态变量的 block1
    void(^block1)(void) = ^{
        NSLog(@"%@",name);
        NSLog(@"block1");
    };
    NSLog(@"block1 => %@",block1);
    
    // 2.引用局部变量的 block2
    int a = 1;
    void(^block2)(void) = ^{
        NSLog(@"%d",a);
        NSLog(@"block2");
    };
    NSLog(@"block2 => %@",block2);
    
    // 3.没有引用外部变量且弱引用修饰的 block3
    void(^__weak block3)(void) = ^{
        NSLog(@"block3");
    };
    NSLog(@"block3 => %@",block3);
    
    // 4.引用静态变量且弱引用修饰的 block4
    void(^__weak block4)(void) = ^{
        NSLog(@"%@",name);
        NSLog(@"block4");
    };
    NSLog(@"block4 => %@",block4);
    
    // 5.引用局部变量且弱引用修饰的 block5
    void(^__weak block5)(void) = ^{
        NSLog(@"%@",a);
        NSLog(@"block5");
    };
    NSLog(@"block5 => %@",block5);
}

输出结果:

2021-01-24 21:47:57.223205+0800 block[10936:934202] block0 => <__NSGlobalBlock__: 0x107102048>
2021-01-24 21:47:57.223361+0800 block[10936:934202] block1 => <__NSGlobalBlock__: 0x107102068>
2021-01-24 21:47:57.223444+0800 block[10936:934202] block2 => <__NSMallocBlock__: 0x600000d25200>
2021-01-24 21:47:57.223518+0800 block[10936:934202] block3 => <__NSGlobalBlock__: 0x1071020a8>
2021-01-24 21:47:57.223605+0800 block[10936:934202] block4 => <__NSGlobalBlock__: 0x1071020c8>
2021-01-24 21:47:57.223719+0800 block[10936:934202] block5 => <__NSStackBlock__: 0x7ffee8afe0f8>

可以看到block 有 3 中类型:NSGlobalBlockNSMallocBlockNSStackBlock

  1. 没有引用任何外部变量,或者引用了静态变量block 都为NSGlobalBlock
  2. 引用了局部变量且没有__weak修饰的blockNSMallocBlock(堆 block)
  3. 引用了局部变量__weak修饰的block,为NSStackBlock(栈 block)

2. block 的循环引用

我们来看下面这两段代码中是否会产生循环引用:

//代码一
NSString *name = @"CJL";
self.block = ^(void){
    NSLog(@"%@",self.name);
};
self.block();

//代码二
UIView animateWithDuration:1 animations:^{
    NSLog(@"%@",self.name);
};

结果:
代码一发生了循环引用,因为在block 内部使用了外部变量 name,导致 block 持有 self,而 block 又是 self 的属性,所以导致了 self 和 block 相互持有
代码二中没有循环引用,虽然也使用了外部变量,但是self 中并没有持有 animation 的 block,没有相互引用。

解决循环引用常见的方式有以下几种:

2.1 weak-strong

__weak 修饰 self 即可,这也是我们日常开发中使用最多的:

typedef void(^CJLBlock)(void);

@property(nonatomic, copy) CJLBlock cjlBlock;

__weak typeof(self) weakSelf = self;
self.cjlBlock = ^(void){
     NSLog(@"%@",weakSelf.name);
}
2.2 __block修饰变量

通过__block修饰对象,主要是因为__block修饰的对象是可以改变的:

__block ViewController *vc = self;
self.block = ^(void){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
        vc = nil;//手动释放
    });
};
// 这里必须调用 block,否则 block 不执行,其内部持有的 vc 不会被释放
self.block();

需要注意的是这里的block必须调用,如果不调用blockvc就不会置空,那么依旧是循环引用selfblock都不会被释放。

2.3 对象 self 作为参数

主要是将对象self作为参数,提供给block内部使用,不会有引用计数问题:

typedef void(^TestBlock)(ViewController *);

@property(nonatomic, copy) TestBlock block;

self. block = ^(ViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
    });
};
self.block(self);
2.4 使用NSProxy虚拟类

具体使用可以看这篇文章:NSTimer循环引用的解决办法

3. block 底层分析

定义一个block.c文件:

#include <stdio.h>

int main(){
    void(^testBlock)(void) = ^{
        printf("123456");
    };
    testBlock();
    return 0;
}

通过下面的命令,将 block.c编译成 block.cpp文件:

xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c

block在底层被编译成了下面的形式:

// 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 的内部实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("123456");
}

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(){
    void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    return 0;
}

// ********main()函数的简化*********
void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); //构造函数
testBlock->FuncPtr(testBlock); // 调用 block 执行

首先是testBlock 赋值,其实是调用了__main_block_impl_0 结构体同名的构造函数,内部对implDesc进行了赋值操作,重点是impl.FuncPtr = fpfp 就是外界传入的__main_block_func_0

然后是testBlock的调用,将 testBlock强制转换为__block_impl*,并调用了FuncPtr(),也就是__main_block_func_0 ()函数,也就执行了整个block

所以block,在编译时被包装成了一个__main_block_impl_0的结构体,并执行了同名的构造函数,将 block 块转换成__main_block_func_0 函数作为参数传入,同时还传入了一些描述信息。执行 block时,转为调用FuncPtr()函数,即__main_block_func_0函数。

4. block变量捕获

我们定义一个int变量a,并在block 中调用:

int main(){
    int a = 10;
    void(^testBlock)(void) = ^{
        printf("%d",a);
    };
    testBlock();
    return 0;
}

编译成cpp 文件,查看源码:

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

        printf("%d",a);
    }

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 a = 10;
    void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    return 0;
}

从上面可以看到__main_block_impl_0结构体中多了一个参数a,并且构造函数中也多了一个参数a

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

说明程序在编译时,自动将外部变量a捕获并添加到了__main_block_impl_0结构体内,并且将参数a的值赋值给结构体中的变量 a

我们继续看下__main_block_func_0函数:

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

        printf("%d",a);
    }

可以看到这里输出的a,并不是 block外界的那个a,而是来自于__cself->a,也就是__main_block_impl_0结构体中的int a 成员变量。这样也就解释了为什么没有__block修饰的变量无法在block内部修改,因为它们完全不是同一个东西。

我们现在用__block 修饰 int a

int main(){
    
    __block int a = 10;
    void(^testBlock)(void) = ^{
        a++;
        printf("%d",a);
    };
    testBlock();
    return 0;
}

编译后查看源码:

// 变量 a 被包裹成一个结构体
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

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

        (a->__forwarding->a)++;
        printf("%d",(a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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 a变量为__Block_byref_a_0结构体对象
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    // 这里将包装后的 __Block_byref_a_0 结构体传入__main_block_impl_0构造函数
    void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    // 调用
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    return 0;
}

我们发现变量int a 被包装成了一个__Block_byref_a_0结构体对象:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

并且这个结构体内部保存了变量 a指针地址和值,然后将__Block_byref_a_0对象传入__main_block_impl_0()构造函数中:

void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

之后在__main_block_func_0函数中,通过变量 a 的指针地址修改a的值:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a)++;
        printf("%d",(a->__forwarding->a));
    }

这里的a++操作,是对a->__forwarding->a的操作,即外界的那个变量a

总结:

上一篇 下一篇

猜你喜欢

热点阅读