1 block 简介
在 Objective-C 中,block 是一种匿名函数(闭包),它可以捕获所在作用域的变量,并且可以在稍后的时间点执行这些代码。
block 并不是继承自 NSObject 的类,而是一种对象或者数据类型。
2 block 类型
全局块 (__NSGlobalBlock__)
-
声明的 block 没有捕获任何变量,那么 block 处于全局区
-
生命周期贯穿整个应用程序执行周期
-
isa 指向 _NSConcreteGlobalBlock
栈块(__NSStackBlock__)
- MRC 环境下捕获外部变量,block 在栈区
- MRC 环境下栈块显式调用拷贝操作,才会将栈区的 block 迁移到堆区。
- 拷贝操作使用
copy,不能使用retain - 创建栈块的作用域内有效,当作用域结束时销毁
- isa 指向 _NSConcreteStackBlock
堆块(__NSMallocBlock__)
-
ARC 环境下捕获外部变量,block 在堆区
-
ARC 环境下编译器会根据情况,自动将栈区的 block 迁移到堆区
-
使用
copy修饰,堆块再进行拷贝操作,引用计数 +1 -
可以在创建堆块的作用域外继续存在,引用计数为 0 时销毁
-
isa 指向 _NSConcreteMallocBlock
3 block 变量捕获
block 可以捕获外部变量,使得 block 内部可以访问和操作外部变量的数值。这种行为被称为变量捕获。
-
局部变量捕获:
当 block 捕获局部变量时,会在内部生成一个包含需要捕获的局部变量的 Block 结构体;
对局部变量的修改只会影响 Block 结构体内的副本,不会影响原始变量;
对于基本数据类型是值拷贝;对于对象类型变量是指针拷贝 -
静态变量捕获:当 block 捕获静态局部变量或全局变量时,block 内部持有对静态变量的指针引用
-
对象变量捕获:当 block 捕获对象类型时,在 block 内部对对象进行强引用
4 block 内部修改外部变量的方式
__block 变量,可以在 block 内部修改外部变量。适用于修改函数内部的局部变量或者对象的成员变量
static 静态变量,在多个 block 中共享同一个变量,并且在不同的作用域内都能修改该变量
5 触发 block 自动调用 copy 的时机
- 当 block 赋值给通过
strong或copy修饰的id或block类型的成员变量时 - 将 block 作为方法或函数的参数传递,被捕获在 block 内部的对象类型变量会被 copy 到堆上
- 将 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 , 在 makeConstraints 或 updateConstraints 中 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.");
}