Objective-C Block | 青训营笔记

193 阅读3分钟

OC Block

这是我参与「第四届青训营 -IOS场」笔记创作活动的第2篇笔记

背景知识

  • block是闭包在OC中的实现
  • block可分配在栈或堆上,也可以是全局的。
  • 栈上的block经过拷贝便会被分配到堆中。
  • block 本质是OC对象,因此它具备引用计数,引用计数为0时将会被回收。

声明与使用

// block 的声明
//returnType (^blockName)(parameters);
int (^addition)(int num1, int num2);

// block 的赋值 
^addition(int num1, int num2) {
  return num1 + num2;
};

// block 的声明与赋值可以写在一起
int (^addition)(int num1, int num2) = ^int(int num1, int num2){
    return num1 + num2;
};

//block 的使用
int onePlusTwo = addition(1, 2);

//省略写法
//有返回值,有入参 - 非空入参的变量名可被省略
int (^addition)(int, int) = ^int(int num1, int num2){
    return num1 + num2;
};

//无返回值,无入参
void(^printHelloWorld)(void)= ^{
    NSLog(@"Hello, World!");
};

当我们需要定义多个拥有相同返回类型和入参类型的block时,可借助 typedef 声明一个block类型来简化代码:

//由于我们在用typedef声明一个新的类型,
//因此类型命名与类的命名相同, 需要首字母大写
typedef int (^Calculator)(int num1, int num2);

Calculator addition = ^(int num1, int num2){ return num1 + num2; };
Calculator subtraction = ^(int num1, int num2){ return num1 - num2; };
Calculator multiplication = ^(int num1, int num2){ return num1 * num2; };
Calculator division = ^(int num1, int num2){ return num1 / num2; };

//使用
int onePlusTwo = addition(1, 2);

Block的内存管理

  1. 全局block: __NSGlobalBlock__, 被分配在数据区(.data段); 条件: block 被定义在全局区或者没有访问自动局部变量。
  2. 栈block: __NSStackBlock__, 被分配在栈区; 条件: block访问了自动局部变量。
  3. 堆block: __NSMallocBlock__, 被分配在堆区; 条件: block 为__NSStackBlock__调用了copy的产物。

注:

  • 我们可以通过 [myBlock class]来查看block的类型。
  • 在ARC机制启动的情况下, 为保证内存的使用安全,在一些默认的情况下系统会默认调用copy操作, 因此, __NSStackBlock__会变成__NSMallocBlock__

Block的变量捕获

  • 默认情况下,不论block 访问的局部变量是基本数据类型还是OC对象, 对其的变量捕获都属于值捕获, 即在block 的初始化时 (block的赋值),已经对局部变量做了值拷贝。因此外部对该变量的任何改动都不会反映到block的局部变量上。
  • 若block 访问的局部变量被关键字 __block修饰, 那么block 对其的变量捕获属于引用捕获, 那么block内部和外部对该变量的改动将会同步。
  • 特别情况: 对于没有被__block修饰的OC对象, 如果block对该对象的某个属性做了访问(例如byteDancer1.name), 那么外部对该属性的改动可能会反映到block的使用时的访问上。因为block只对该OC对象本身(对象指针)进行了值捕获, 而OC对象对属性访问是通过信息传递来实现的, 是实时的,因此会获得变化后的属性的值。

Block的循环引用

在ARC下, 系统会一些默认的情况下系统会对block调用copy操作, 使其变成__NSMallocBlock__, 此时block会有自己的引用计数, 那么就有机会出现循环引用的问题, 比如以下情况:

  • 一个block作为@property被一个OC类所持有 (强引用)。
  • 该block内部通过self对该OC类进行了变量引用, 或者对该OC类的某个隐式变量(_var)进行了访问。

解决方法:

  • 在blcok的外部创建个弱引用对象: __weak typeof(self) weakSelf = self;
  • 在block的内部通过弱引用对象再创建个强引用对象, 并使用该强引用对象: _strong typeof(weakSelf) strongSelf = weakSelf;

Q: 为什么在block内部不直接用weakSelf, 而是手动_strong下?

A: 防止在block内部代码执行期间, self的引用计数变成0且被释放。