前言
本文作为《Objective-C 高级编程》读书笔记的第二篇,给大家带来的是关于 Blocks 的知识点总结。
概念
Blocks 是 C 语言的扩充功能,可以用一句话来表示 Blocks 的扩充功能:带有自动变量(局部变量)的匿名函数。(Blocks 是闭包在 OC 语言中的实现,并不是 iOS 独有的概念,在 C++、Java 等语言也有实现闭包,只是名称不同而已)
优势
- 可代替 Delegate 完成回调,而不需要像 Delegate 那样繁琐
- 在某些方面,可代替 selector(如 NSNotificationCenter 在 addObserver 的时候,可以使用 block,而不用单独定义方法)
- 延长对象的生命周期(Block 会自动持有对象)
- 提高代码的复用性和可读性
- 常用于:View 动画、GCD、网络异步请求
语法
关于 Blocks 的语法,看下面一张图就可以啦:
Blocks 的实现
Blocks 的数据结构
对应的结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ }; |
通过该图,我们可以知道,一个 Block 实例实际上由 6 部分构成:
-
isa 指针: 所有对象都有该指针,用于实现对象相关的功能
-
flags: 用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用;
-
reserved: 保留变量;
-
invoke: 函数指针,指向具体的 block 实现的函数调用地址;
-
descriptor: 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针;
-
variables: capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中;
Block 存储域
Objective-C 中的 Stack 和 Heap
首先所有的 Objective-C 对象都是分配在 Heap 的。 在 OC 最典型的内存分配与初始化就是这样的:
NSObject *obj = [[NSObject alloc] init];
一个对象在 alloc 的时候,就在 Heap 分配了内存空间。
Stack 对象通常有速度的优势,而且不会发生内存泄露问题。那么为什么 OC 的对象都是分配在 Heap 的呢? 原因在于:
-
Stack 对象的生命周期所导致的问题。例如一旦函数返回,则所在的 Stack Frame(栈帧)就会被销毁。那么此时返回的对象也会一并销毁。这个时候我们去 retain 这个对象是无效的。因为整个 Stack Frame 都已经被销毁了。简单而言,就是 Stack 对象的生命周期不适合 OC 的引用计数内存管理方法。
-
Stack 对象不够灵活,不具备足够的扩展性。创建时长度已经是固定的,而stack对象的拥有者也就是所在的 Stack Frame
Block 类型
应用程序的内存分配:
在 OC 中,一共有 3 种类型的 Block:
_NSConcreteGlobalBlock
_NSConcreteGlobalBlock:全局的静态 Block,不会访问任何外部变量。
_NSConcreteStackBlock
_NSConcreteStackBlock:保存在栈中的 Block,当函数返回时会被销毁。(ARC 中系统实现了自动 copy, 将创建在栈上的 Block 自动拷贝到堆上,所以不存在此类型的 Block)
_NSConcreteMallocBlock
_NSConcreteMallocBlock:保存在堆中的 Block,当引用计数为 0 时会被销毁。(即成为正常的 OC 对象)
Block 循环引用
如果在 Block 中使用附有 __strong 修饰符的对象类型自动变量,那么当 Block 从栈复制到堆时,该对象为 Block 所持有,于是便导致了循环引用的产生。
如图所示:self 持有 Block,Block 持有 self,这正是循环引用。
MRC
在 MRC 下,使用 __block 说明符来避免 Block 中的循环引用。
这是由于当 Block 从栈复制到堆时,若 Block 使用的变量为附有 block 说明符的 id 类型或对象类型的自动变量,不会被 retain;若 Block 使用的变量为没有 block 说明符的 id 类型或对象类型的自动变量,则被 retain;若 Block 使用的变量为没有 __block 说明符的 id 类型或对象类型的自动变量,则被 retain。
ARC
在 ARC 下,为了避免这种情况发生,可以在变量声明时用 weak 修饰符修饰变量 self,让 block 不强引用 self,从而破除循环。iOS4 和 Snow Leopard 由于对 weak 的支持不够完全,可以用 unsafe_unretained 代替。
使用 Block 成员变量避免循环引用:
比较
下面对使用 block 变量避免循环引用的方法和使用 weak 修饰符及 __unsafe_unretained 修饰符避免循环引用的方法做个比较。
使用 __block 变量的优点如下:
- 通过
__block
变量可控制对象的持有期间 -
在不能使用
__weak
修饰符的环境中不使用__unsafe_unretained
修饰符即可(不必担心悬垂指针)在执行 Block 时可动态地决定是否将 nil 或其他对象赋值在
__block
变量中。
使用 __block 变量的缺点如下:
-
为避免循环引用必须执行 Block
存在执行了 Block 语法,却不执行 Block 的路径时,无法避免循环引用。若由于 Block 引发了循环引用时,根据 Block 的用途选择使用
__block
变量、__weak
修饰符或__unsafe_unretained
修饰符来避免循环引用。
要点
-
Block 执行的代码其实在编译的时候就已经准备好了
-
本身 Block 就是一个普通的 OC 对象。正因为它是对象,Block 可以被作为参数传递,可以作为返回值从一个方法返回,可以用来给变量赋值
-
__block 修饰符在 MRC 下不会进行引用计数加 1,而 ARC 下则会加 1
-
对于 Block 外的变量引用,Block 默认是将其复制到其数据结构中来实现访问的
-
对于用 __block 修饰的外部变量引用,Block 是复制其引用地址来实现访问的
原文: