block 底层原理分析(一)

1,821 阅读4分钟

block 的类型

在我们的日常开发过程中相信大家都会用到 block,但是 block 有哪些类型,你又是否知道呢?下面我们来看一下 block 的类型区分。

block 三种类型

image.png

通过代码演示,我们可以看出,输入了三种类型的 block,下面我们来介绍下这三种 block 类型:

  • GlobalBlock

    • 位于全局区
    • block 内部不使用外部变量,或者只使用静态变量和全局变量
  • MallocBlock

    • 位于堆区
    • block 内部使用变量或者 oc 属性,并且赋值给强引用或者 copy 修饰的变量
  • StackBlock

    • 位于栈区
    • MallocBlock 一样,可以在内部使用局部变量或者 oc 属性,但不能赋值给强引用或者 copy 修饰的变量

block 拷贝到堆区的条件

  • 手动 copy
  • block 作为返回值
  • 被强引用或者 copy 修饰
  • 系统 api 包含 usingBlock

相关案例

  • 案例一

image.png

这里我们自定义了一个 _CXBlock 类型的结构体,我们对 weakBlock 进行强转并赋值给 blc,这样我们就可以对 block 内的数据结构进行修改,因为 weakBlock, strongBlock, strongBlock1 它们指向的是同一块内存区间,所有当我们把 blcinvoke 属性设置为 nil 后,strongBlock1() 的执行会报错,找不到函数执行。但是当我们把 id __strong strongBlock = weakBlock 改为 id __strong strongBlock = [weakBlock copy] 之后就可以解决这个问题,这是因为把栈 block 拷贝之后 blc 就为堆 block

  • 案例二

image.png

因为 block 使用外部变量的时候会进行捕获,所以就会对引用计数加 1,第一次打印 3 是因为栈区 block 跟堆区 block 各引用了一次,打印 4 是因为这是一个栈区 block,所以引用计数加 1,打印 5 是因为 weakBlock 拷贝之后变为了堆区 block,又会对引用计数加 1。

  • 案例三

image.png

这是一个关于 block 释放时机的问题,strongBlock 是栈区 block,所以 strongBlock 的作用域是中间的大括号结束,所以 weakBlock = strongBlock 可以赋值成功,这时候 weakBlock 也是栈区 block,所以它的作用域在最外层大括号结束的时候,所以 weakBlock() 可以执行成功。如果 strongBlock 是堆 blockweakBlock() 就不会执行。

block 循环引用

对象的释放条件

image.png

循环引用条件

image.png

循环引用案例

    // 循环引用
    self.name = @"chenxi";
    self.block = ^{
        NSLog(@"%@",self.name);
    };

类似这样一段代码,这种情况下就会出现循环引用,我们可以用 weakSelf 的方式来解决这个问题,代码如下:

  • 解决循环引用方法一
    self.name = @"chenxi";
    __weak __typeof(self)weakSelf = self;
    self.block = ^{
        NSLog(@"%@",weakSelf.name);
    };

虽然 weakSelf 的方式确实能解决循环引用问题,但是这种方式是不完善的,可以看如下案例:

image.png

类似这种,我们在打印的时候加个延时,这时候有种场景,当我们页面返回过快的时候,还没来得及打印,name 就已经释放了,所以打印为 null。这时候解决这种问题我们需要用到 strongSelf,如下:

    self.name = @"xhenxi";
    __weak __typeof(self)weakSelf = self;
    self.block = ^{
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();

这里 strongSelf 只是一个临时变量,延长了 weakSelf 的释放时间,但是 strongSelf 作用域只是在 block 函数里面,block 函数执行完 strongSelf 就会释放。

  • 解决循环引用方法二
- (void)viewDidLoad {
    [super viewDidLoad];
    self.name = @"xhenxi";
    __block ViewController *vc = self;
    self.block = ^{
        NSLog(@"%@",vc.name);
        vc = nil;
    };
    self.block();
}

这里用了一个临时变量 vc 接收 self,在打印完毕后把 vc 置为 nil

  • 解决循环引用方法三
typedef void(^CXBlock)(ViewController *vc);
@interface ViewController ()
@property (nonatomic, copy) CXBlock block;
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.name = @"xhenxi";
    self.block = ^(ViewController *vc) {
        NSLog(@"%@",vc.name);
    };
    self.block(self);
}

这里是把 self 作为一个参数传递给 block

循环引用相关面试题

  • 试题一
static ViewController *staticSelf_;

- (void)blockWeak_static {
    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
}

- (void)dealloc {
    NSLog(@"dealloc 调用");
}

类似这样一段代码,我们把 weakSelf 赋值给了一个全局静态变量 staticSelf_,运行指挥,dealloc 方法并没有走,会导致不释放。这是因为 weakSelf 与 self 指向的是同一片内存空间,所以把 weakSelf 赋值给 staticSelf_ 之后会导致内存不释放。

  • 试题二
typedef void(^CXBlock)(void);
@interface ViewController ()
@property (nonatomic, copy) CXBlock block;
@property (nonatomic, copy) CXBlock doWork;
@property (nonatomic, copy) CXBlock doStudent;
@end

- (void)block_weak_strong {
    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@"%@", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}

- (void)dealloc{
    NSLog(@"dealloc 调用");
}

这里也会出现循环引用问题,因为 doStudent 会对 strongSelf 进行捕获,所以 strongSelf 引用计数会加 1,所以出了 doWork 函数之后, strongSelf 会释放不掉。