Chapter 6. Blocks and Grand Central Dispatch
<br />
Item 40: Avoid Retain Cycles Introduced by Blocks Referencing the Object Owning Them
<br />
这一条讲避免因为使用block而出现保留环的问题。
保留环的出现是因为当block里引用了某个对象的实例变量的时候,这个对象也会被引用。这是一种不太容易被发现的情况。如果是直接引用控制器对象,控制器对象又引用block的话,就很容易发现。环中涉及三个以上对象的时候就容易混乱,我一般得通过画图分析。
以前只知道用weak关键字来打破保留环,这篇提供了一个别的思路,就是在所执行的任务结束时,适时地把不再用的对象释放掉,这样环里的一个箭头就不存在了,整个环也就被打破了。例子给的比较长,这里只放一下打破环的部分:
[_networkFetcher startWithCompletionHandler:^(NSData *data) {
NSLog(@“Request for URL %@ finished”, _networkFetcher.url);
_fetchedData = data;
_networkFetcher = nil;
}];
这里是数据取得以后,fetcher就没有用了,于是及时释放掉。
还有一个例子:
- (void)p_requestCompleted {
if (_completionHandler) {
_completionHandler(_downloadedData);
}
self.completionHandler = nil;
}
这里直接释放了block,也就是说如果block用完就没有用了,所以不再引用它。
总之是有很多灵活的方法来打破保留环,可以具体问题具体分析。
<br />
Item 41: Prefer Dispatch Queues to Locks for Synchronization
<br />
这一节其实讲的是dispatch queue的用法。
基本的同步与异步就不说了。这里有一个小细节:
- (void)setSomeString:(NSString *)someString {
dispatch_async(_syncQueue, ^{
_someString = someString;
});
}
这是一个setter,里面是异步实现的。文中提到这里可能会有性能问题,因为使用异步派发的话,block会被copy,这里有一个copy时间。所以需要权衡copy花的时间和同步派发所花的时间,如果前者更多,就没必要使用异步派发。
文中还介绍了设计getter和setter的思想,获取是可以并发的,而设置需要同步执行,也就是说设置一个变量的时候,不应该再有其他的读写操作。
具体实现是采用barrier,常见的有两个函数:
- dispatch_barrier_async
- dispatch_barrier_sync
共同点:都是当队列里正在执行的block执行完毕后,再开始执行这个操作,执行时不会有别的block同时执行,当这个操作执行完毕后别的操作才会再开始执行。
关于不同点,文档里是这么说的:
dispatch_barrier_async: Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked.
dispatch_barrier_sync: Submits a barrier block to a dispatch queue for synchronous execution. Unlike dispatch_barrier_async, this function does not return until the barrier block has finished. Calling this function and targeting the current queue results in deadlock.
所以区别是在于是不是立即返回。但是dispatch_barrier_sync里提到的这个死锁情况我不是特别明白。(好了我现在明白了!sync都是会有这种死锁出现,阻塞了当前线程同时又在等当前线程的任务执行。)
文中的setter和getter的写法:
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *) someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString {
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}
<br />
Item 42: Prefer GCD to performSelector and Friends
<br />
这一节讲performSelector的局限性。
performSelector确实挺少见的,除了在runtime讲动态绑定的部分见过,平时比较少见,不知道它有多线程相关的功能。它的局限性在于返回值和参数,返回值是id,参数最多只有两个,不够灵活。并且在runtime动态绑定时,由于ARC不再根据方法名采用自动释放,还有内存泄露的可能性存在。
所以采用GCD办法来代替performSelector相关的方法。如果需要延后执行,应该选择dispatch_after
,而不是performSelector:withObject:afterDelay:
。如果是主线程执行,应该选择dispatch_get_main_queue()
作为dispatch queue,而不是调用performSelectorOnMainThread:withObject:waitUntilDone:
。