1.理解"块"这一概念
- 块是 C,C++,Object-C 中的语法闭包.
- 块可以接收参数,也可以返回参数.
- 如果块里捕获的是对象类型的变量,那么块就会自动保存它,系统在释放这个块的时候,也会将其一并释放.
- 因为每个实例变量的个数及对象所包含的关联数据互不相同,所以每个对象所占内存区域也有大有小.
- 在块里面捕获所持有的对象,拷贝的并不是对象本身,而是指向对象的指针变量.
- 除了"栈块"和"堆块"之外,还有一种叫"全局块(gloab block)".全局块的拷贝操作是个空的操作,因为全局块决不可能为系统所回收.实际上相当于单利.
- 块可以分配在堆或栈上,也可以是全局的.分配在栈上的块可拷贝到堆里,这样的话,就和标准的 Object-C 对象一样,具备引用计数了.
2.为常用的块类型创建 typedef
- 以 typedef 重新定义块类型,可令块变量用起来更加简单.
- 定义新类型时,必须遵从现有的,命名习惯,勿使其名称与别的类型相冲突.
- 每个块都具备其"固定类型(inherent type)",因而可将其赋值给适当类型的变量.
- 使用类型定义后,当你打算重构块的类型签名时会很方便.
- 最好在使用块类型的类中定义这些 typedef, 而且还应该把这个类的名字加在由 typedef 所定义的新类型名前面,这样就可以阐明块的用途.
- 不妨为同一个块签名定义多个类型别名.如果要重构的代码使用了块类型的某个别名,那么只需修改相应 typedef 中的块签名,无须改动其他 typedef.
3.用 hander 块降低代码分散程度
- 在创建对象时,可以使用内联的 hander 块将相关业务逻辑一并声明.
- 在有多个实例要监控时,如果采用委托模式,那么经常需要根据传入的对像来切换,而若改用 hander 块来实现,则可直接将块与相关对象放在一起.
- 设计 API 时如果用到了 hander 块,那么可以增加一个参数,使调用者可通过此参数来决定应该吧块安排在哪个队列上执行.
4.用块引用其所属对象时不要出现保留环
- 如果块所捕获的对象直接或间接的保留了块本身,那么就得当心保留环的问题.
- 一定要找个适当的时机解除保留环,而不能把责任退给 API 的调用者.
4.多用派发队列,少用同步锁
- @synchronized 属性原子性内部实现锁,可以提供某种程度的"线程安全(therad safety)",但却无法保证访问该对象时绝对是线程安全.
- 简单而高效的代替同步块或锁对象的方法,就是使用"串行同步队列(serial synchoronization queue)".
- 在并发队列中,栅栏块必须单独执行,不能与其他块并行.这支队并发队列有意义,因为串行队列中的块总是按顺序逐个来执行的.
- 派发队列可用来表示同步语义(synchroniztion semantic),这种做法要比使用@ synchronized 块或 NSLock 对象更简单.
- 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步操作,而这么做却不会阻塞执行异步派发线程.
- 使用同步队列及栅栏块,可以令同步行为更加高效.
5.多用 GCD, 少用 performSelector 方法
- 用 performSelector:系类方法,编译器并不知道将要调用的选择子是什么,因此,也就不了解其方法的签名及返回值,甚至连是否有返回值都不清楚.而且,由于编译器不知道方法名,所以就没办法运用 ARC 的内存管理规则来判定返回值是不是应该释放.鉴于此, ARC 采用了比较谨慎的做法,就是不加添释放操作.然而这么做很可能导致内存泄露,因为方法在返回对象时有可能已经将其保留了.
- performSelector系类方法所能处理的选择子太过局限了,选择子的返回值类型及发送给方法的参数个数都受限制.
- 如果想把任务放到另一个线程上执行,那么最好不要使用 performSeletor 系列方法,而是应该把任务封装到块面,然后调用大中枢派发机制的相关方法来实现.
6.掌握 GCD 及操作队列的使用时机
GCD 是纯 C 的 API, 而操作队列是 Object-C 的对象.咋 GCD 中,任务用块来表示,而快是轻量级的数据结构.与之相反,"操作(operation)"则是更为重量级的 Object-C 的对象.
-
使用 NSOperationQueue 及 NSOperation 的好处:
a.取消某个操作,在任务执行之前,可以在 NSOperation 对象上调用 cancel 方法.不过,已经启动的任务无法取消.
b.制定操作间的依赖关系.一个操作可以依赖其它多个操作.
c. 通过键值观察机制监控 NSOperation 对象的属性.例:isCancelled 属性来判断任务是否取消. isFinished 属性判断任务是否完成.
d. 制定操作的优先级.
e. 重用 NSOperation 对像.
在解决多线程与任务管理问题时,派发队列并非唯一的方案.
操作队列提供了一套高层的 Object-C API,能实现纯 GCD 所具备的大部分功能,而且还能完成一些更为复杂的操作,那些操作若改用 GCD 来实现,则需另外编写代码.
7.通过 Dispath Group 机制,根据系统资源状态来执行任务
- dispath_apply 所用的队列可以是并发队列或者串行队列. disapath_apply 会阻塞线程,直到所有的任务都执行完毕为止.
- 假如把块派发给了当前队列(或者体系中高于当前队列的某个串行队列),就会导致死锁.若想在后台执行任务,则应使用 dispath group.
- 一系列任务可归入一个 dispath group 之中.开发者可以在这组任务执行完毕时获取通知.
- 通过 dispath group,可以在并发式派发队列里同时执行多项任务.此时 GCD 会根据系统的资源来调度这些并发的任务.开发者若自己来实现此功能,则需编写大量代码.
8.使用 dispath_once 来执行只需运行一次的线程安全代码.
- 经常需要编写"只需要执行一次的线程安全代码(thread-safe single-code execution)".通过 GCD 所提供的 dispath_once 函数,很容易就能实现此功能.
- 标记应该声明在 staic 或者 global 作用域中,这样的话,在把只要执行一次的块传给 dispath_once 函数时,传进去的标示也是相同的.
9.不要使用 dispath_get_current_queue
- dispath__get_ _ current_queue 在iOS6.0版本起,已经正式废弃使用了,不过 macOs 系统在10.8版本也尚未废弃.虽然说如此,但是在 macOs 系统还是要避免使用它.
- dispath__get_ _ current_queue 函数行为常常与开发者所预期的不同,此函数废弃,只应用作调试.
- 由于派发队列是按层级来组织的,所以无法但用某个队列对象来描述"当前队列"这一概念.
- dispath__get_ _ current_queue 函数用于解决由不可重入的代码所引发的死锁,然而能用此函数解决的问题,通常也能改用"队列特定数据"来解决.
- dispath__queue_ _ set_specific "队列特定数据",用来给相应队列添加信息.
void dispath_queue_set_specific(dispath_queue_t queue,
const void *key,
void *context,
dispath_funcation_t destructor);
queue:设置信息的队列;
key:函数就是按照这个 key 来比较键的.
context:对队列特定数据的关联引用,这个对象的内存管理要自己处理;
destructor:解析构造函数,对于给定的键来说,当队列所占内存为系统所回收,或者有新的值与键相关联时,原有的值对象就会被移除,而析构函数也会此时运行.dispath__funcation _t 类型的定义如下:
typedef void (*dispath_funcation_t)(void *)
由此可见,析构函数只能带一个指针参数且返回值为 void.