当前多线程编程的核心就是“块”(block)与“大中枢派发”(Grand Central Dispatch,GCD)。
块:一种可在C、C++及Objective-C代码中使用的“词法闭包”
GCD是一种与块有关的技术,它提供了对线程的抽象,而这种抽象则基于“派发队列”(dispatch queue)。
37、理解“块”这一概念
^ {
// Block implementatin here
}
_ _block NSInteger count = 0
void (^someBlock) () = ^ {
count ++;
};
全局块、栈块及堆块
栈块 :定义块的时候,其所占的内存区域是分配在栈中的。
void (^block)();
if (/*A*/) {
block = ^{
NSLog("Block A");
}
}
else {
block = ^{
NSLog("Block B");
}
}
block();
堆块:可给块对象发送copy消息以拷贝之,这样的话,就可以把块从栈复制到堆了;
void (^block)();
if (/*A*/) {
block = [^{
NSLog("Block A") ;
}copy]
}
else {
block = [^{
NSLog("Block B");
}copy]
}
block();
全局块
void (^someBlock) () = ^ {
};
块是C、C++、Objective-C中的词法闭包
块可接受参数、也可返回值
块可以分配在栈或堆上,也可以是全局的。分配在栈上的块可拷贝到堆里,这样的话,就和标准的Objective-C对象一样,具备引用计数了;
38、为常用的块类型创建typedef
以typedef重新定义块类型,可令块比变量用起来更加简单;
定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型相冲突
不妨为同一个块签名定义多个类型别名。如果要重构的代码使用了块类型的某个别名,那么只需修改相应typedef中的块签名即可,无须改动其他typedef;
void (^SomeBlock) ();
SomeBlock someBlock = = ^ {
};
39、用handler块降低代码分散程度
在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明;
在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler来实现,则可直接将块与相关对象放在一起
设计API时如果用到了handler块,那么可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行;
40、用块引用其属性对象时不要出现保留环
如果块所捕获的对象直接或间接地保留了块本身,那么就得当心保留环问题
一定要找个适当的时机解除保留环,而不能把责任推给API的调用者
41、多用派发队列,少用同步锁
派发队列可用来表述同步语义,这种做法要比使用@synchronized块或NSLock对象更简单
将同步与异步派发结合起来。可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程;
使用同步队列及栅栏块,可以令同步行为更加高效
42、多用GCD、少用performSelector系列方法
peformSelector系列方法在内存管理方面容易有疏失,它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法
performSelector系列方法所能处理的选择子太过局限了。选择子的返回值类型及发送给方法的参数个数都受到限制
如果想把任务放在另一个线程上执行,那么最好不要用performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现
43、掌握GCD及操作队列的使用时机
GCD与操作队列(NSOperationQueue)
GCD是纯C的API,在GCD中,任务用块来表示,而块是个轻量级数据结构,
操作对象是个更为重量级的Objective-C对象,底层是用GCD来实现的
操作对象好处:
1、取消某个操作
2、指定操作间的依赖关系
3、通过键值观测机制监控NSOperation对象的属性
4、指定操作的优先级
在解决多线程与任务管理问题上,派发队列并非唯一方案
操作队列提供了一套高层的Objective-C API,能实现纯GCD所具备的绝大部分功能,而且还能完成一些更为复杂的操作,那么操作若改用GCD来实现,则需另外编写代码
44、通过Dispatch Group 机制,根据系统资源状况来执行任务
一系列任务可归入一个dispatch group之中,开发者可以在这组任务执行完毕时获得通知。
通过dispatch group,可以在并发式派发队列里同时执行多项任务,此时GCD会根据系统资源状况来调度这些并发执行的任务,开发者若自己来实现此功能,则需编写大量代码
45、使用dispatch_once来执行只需运行一次的线程安全代码
+ sharedInstance {
static Class *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
})
return sharedInstance;
}
经常需要编写“只需执行一次的线程安全代码”。通过GCD所提供的dispatch_once 函数,很容易就能实现此功能。
标记应该声明在static或global作用域中,这样的话,在把只需执行一次的块传给dispatch_once函数时,传进去的标记也是相同的。
46、不要使用dispatch_get_current_queue
dispatch_get_current_queue函数的行为常常与开发者预期的不同,此函数已经废弃,只应做调试之用;
由于派发队列时按层级来组织的,所以无法单用某个队列队对象来描述“当前队列”这一概念;
dispatch_get_current_queue函数用于解决由不可重入的代码所引发的死锁,然而能用此函数解决的问题,通常也能改用“队列特定数据”来解决;
dispatch_get_current_queue所返回的“当前队列”,总是其调用API时指定的那个,但实际上返回的却是API内部的那个同步队列;