iOS Block

173 阅读4分钟

1 block 简介

在 Objective-C 中,block 是一种匿名函数(闭包),它可以捕获所在作用域的变量,并且可以在稍后的时间点执行这些代码。

block 并不是继承自 NSObject 的类,而是一种对象或者数据类型。

2 block 类型

全局块 (__NSGlobalBlock__)

  1. 声明的 block 没有捕获任何变量,那么 block 处于全局区

  2. 生命周期贯穿整个应用程序执行周期

  3. isa 指向 _NSConcreteGlobalBlock

栈块(__NSStackBlock__)

  1. MRC 环境下捕获外部变量,block 在栈区
  2. MRC 环境下栈块显式调用拷贝操作,才会将栈区的 block 迁移到堆区。
  3. 拷贝操作使用 copy,不能使用 retain
  4. 创建栈块的作用域内有效,当作用域结束时销毁
  5. isa 指向 _NSConcreteStackBlock

堆块(__NSMallocBlock__)

  1. ARC 环境下捕获外部变量,block 在堆区

  2. ARC 环境下编译器会根据情况,自动将栈区的 block 迁移到堆区

  3. 使用 copy 修饰,堆块再进行拷贝操作,引用计数 +1

  4. 可以在创建堆块的作用域外继续存在,引用计数为 0 时销毁

  5. isa 指向 _NSConcreteMallocBlock

3 block 变量捕获

block 可以捕获外部变量,使得 block 内部可以访问和操作外部变量的数值。这种行为被称为变量捕获。

  1. 局部变量捕获
    当 block 捕获局部变量时,会在内部生成一个包含需要捕获的局部变量的 Block 结构体;
    对局部变量的修改只会影响 Block 结构体内的副本,不会影响原始变量;
    对于基本数据类型是值拷贝;对于对象类型变量是指针拷贝

  2. 静态变量捕获:当 block 捕获静态局部变量或全局变量时,block 内部持有对静态变量的指针引用

  3. 对象变量捕获:当 block 捕获对象类型时,在 block 内部对对象进行强引用

4 block 内部修改外部变量的方式

__block 变量,可以在 block 内部修改外部变量。适用于修改函数内部的局部变量或者对象的成员变量

static 静态变量,在多个 block 中共享同一个变量,并且在不同的作用域内都能修改该变量

5 触发 block 自动调用 copy 的时机

  1. 当 block 赋值给通过 strongcopy 修饰的 idblock 类型的成员变量时
  2. 将 block 作为方法或函数的参数传递,被捕获在 block 内部的对象类型变量会被 copy 到堆上
  3. 将 block 存储在集合类中(如 NSArray、NSDictionary)时,因为集合类要求元素必须是对象类型,所以会自动将 block 进行 copy 操作

6 __block 和 __weak 修饰符的区别

__block 修饰符:

  • 在 block 内捕获外部变量的指针,实现对变量的修改
  • 能修饰对象类型的变量和基本数据类型
  • 在 ARC 下,__block 修饰的变量是强引用,可能会导致循环引用的问题
  • 在 MRC 下,__block 不会改变具体的内存管理方式,仍需要手动管理引用计数

__weak 修饰符:

  • 定义弱引用,不会增加对象的引用计数,当对象被释放后,自动设置为 nil
  • 只能修饰对象类型的变量
  • 只能在ARC下使用,用于解决 block 中可能引起的循环引用问题

7 __weak 和 __strong 在解决循环引用中的作用

__weak 修饰的变量不会增加对象的引用计数,也就不会造成 block 强持有外部变量,解决循环引用的问题。

当在 block 内部执行异步操作或者延迟操作时,此时引用的外部变量可能会变成nil,此时在 block 内部通过 __strong 修饰外部变量,block 会在执行过程中强持有这个变量,也就不会出现 nil 的情况。

Masonry 需要用 __weak 修饰吗?

Masonry 内部并没有使用 __weak , 在 makeConstraintsupdateConstraints 中 View 并没有持有 block ,这个 block 只是一个 栈block。当执行完 block(constraintMaker) 就出栈释放掉了,所以不会造成循环引用。 

8 数组能添加一个 block 吗?数组添加一个 block 之后再取出来,这个block还有用吗?

在 Objective-C 中,数组是存储对象的集合,而 block 也是 Objective-C 对象的一种。当将一个对象添加到数组中时,数组会对这个对象进行强引用。

所以数组是可以添加 block,取出后仍然可用。当将一个 block 添加到数组中时,block 的引用计数会增加1。当从数组中取出 block 时,block 的引用计数不会发生改变,因为它仍然被数组持有。

9 创建一个可以被取消执行的 block

typedef void (^CancelableBlock)();

+ (CancelableBlock)dispatch_async_with_cancelable:(void (^)(void))block {
    __block BOOL isCanceled = NO;

    CancelableBlock cancelableBlock = ^{
        isCanceled = YES;
    };

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 在执行前检查 isCanceled 变量
        if (!isCanceled) {
            // 执行 block
            dispatch_async(dispatch_get_main_queue(), block);
        } else {
            NSLog(@"Block execution canceled.");
        }
    });

    return cancelableBlock;
}

// 在另一个方法中修改 isCanceled 变量的值
- (void)cancelExecution:(CancelableBlock)cancelBlock {
    cancelBlock(); // 这会设置 isCanceled 为 YES
    NSLog(@"Execution canceled.");
}