多线程编程的核心就是"块"(block)与"大中枢派发"(Grand Central Dispatch,GCD)
块 是一种可在C、C++以及Objective-C代码中使用的"词法闭包"(lexical closure),它极为有用,这主要是因为借由此机制,开发者可将代码像对象一样传递,令其在不同环境(context)下运行。还有个关键的地方是,在定义"块"的范围内,它可以访问到其中的全部变量。
"GCD" 是一种与块有关的技术,它提供了对线程的抽象,而这种抽象则基于"派发队列"(dispatch queue)。开发者可将块排入队列中,由GCD负责处理所有调度事宜。GCD会根据系统资源情况,适时地创建、复用、摧毁后台线程(background thread),以便处理每个队列。此外,使用GCD还可以完成常见的编程任务,如编写"只执行一次的线程安全代码",或者根据可用的系统资源来并发执行多个操作。
三十七、理解"块"这一概念
块与函数类似,只不过是直接定义在另一个函数里的,和定义它的那个函数共享同一个范围内的东西。块类型的语法结构如下:
return_type (^block_name)(parameters)
实际定义和使用如:
int (^addBlock)(int a, int b) = ^(int a, int b){
return a + b;
}
int result = addBlock(2,5); //result = 7
块的强大之处是:在声明它的范围里,所有变量都可以为其所捕获。这也就是说,那个范围里的全部变量,在块依然可用。
默认情况下,为块所捕获的变量,是不可以在块里修改的。不过,声明变量的时候可以加上__block修饰符,这样就可以在块内修改了。
如果块所捕获的变量是对象类型,那么就会自动保留它。系统在释放这个块的时候,也会将其一并释放。 块本身也和其他对象一样,有引用计数。
块总能修改实例变量,所以在声明时无需加__block。 当块是属于类的一个属性时,如果在块内使用到类的self,为了避免产生保留环,需要使用weakSelf,而不是self。
__weak typeof(self) weakSelf = self;
块可以分配在栈或堆上,也可以是全局的。分配在栈上的块可拷贝到堆里,这样的话,就和标准的Objective-C对象一样,具备引用计数了。
三十八、为常用的块类型创建typedef
为了隐藏复杂的块类型,需要用到C语言中名为"类型定义"的特性,typedef关键字用于给类型起个易读的别名。
typedef int(^EOCSomeBlock)(BOOL flag, int value);
声明变量时,要把名称放在类型中间,并在前面加上"^"符号,而定义新类型时也得这么做。
此后,可以直接使用新类型即可:
EOCSomeBlock block = ^(BOOL flag,int value){
//implementation
}
类里面有些方法可能需要用块来做参数,比如执行异步任务时所用的"completion handler"参数就是块,凡是遇到这种情况,都可以通过定义别名使代码变得更加易读。
-(void)startWithCompletionHandler:(void(^)(NSData *data,NSError *error))complection;
//使用typedef起个别名:
typedef void(^EOCCompletionHandler)(NSData *data,NSError *error);
-(void)startWithCompletionHandler:(EOCCompletionHandler)completion;
要点:
- 以typedef重新定义块类型,可令块变量用起来更加简单
- 定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型冲突
- 不妨为同一个块签名定义多个类型别名。如果要重构的代码使用了块类型的某个别名,那么只需修改相应typedef中的块钱名即可,无须改动其他typedef
三十九、用handler块降低代码分散程度
要点:
-(void)fetchFooData{
NSURL *url = [NSURL alloc] initWithString:@"http://www.baidu.com"];
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithUrl:url];
[fetcher startWithCompletionHandler:^(NSData *data){
_fetchedFooData = data;
}
}
与使用委托模式的代码相比,用块写出来得代码更为简洁。异步任务执行完毕后所需运行的业务逻辑,和启动异步任务所用的代码放在了一起。 委托模式有个缺点,如果类要分别使用多个获取器下载不同数据,那么就得在delegate回调方法里根据传入的获取器参数来切换。
要点:
- 在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明
- 在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler块来实现,则可直接将块与相关对象放在一起
- 设计API时如果用到了handler块,那么可以直接增加一个参数模,使调用者可以通过此参数来决定应该把块安排在哪个队列上执行
四十、用块引用其所属对象时不要出现保留环
#import <Foundation/Foundation.h>
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFetcher : NSObject
@property (nonatomic , strong , readonly) NSURL *url;
-(id)initWithURL:(NSURL *)url;
-(void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end
#import "EOCNetworkFetcher.h"
@interface EOCNetworkFetcher ()
@property (nonatomic , strong , readwrite) NSURL *url;
@property (nonatomic , copy)EOCNetworkFetcherCompletionHandler completionHandler;
@property (nonatomic , strong)NSData *downLoadedData;
@end
@implementation EOCNetworkFetcher
-(id)initWithURL:(NSURL *)url{
if (self = [super init]){
_url = url;
}
return self;
}
-(void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion{
self.completionHandler = completion;
//Start the request
//When request is finished,p_requestCompleted is called;
}
-(void)p_requestCompleted{
if (_completionHandler){
_completionHandler(_downLoadedData);
}
self.completionHandler = nil;//请求处理完毕,就解除保留环
}
@end
其他类中使用时:
#import <Foundation/Foundation.h>
@interface FetcherData : NSObject
@end
#import "FetcherData.h"
#import "EOCNetworkFetcher.h"
@implementation FetcherData
{
EOCNetworkFetcher *_netWorkfetcher;
NSData *_fetcherdData;
}
-(void)downloadData{
//begin
NSURL *url = [[NSURL alloc]initWithString:@"www.google.cn"];
_netWorkfetcher = [[EOCNetworkFetcher alloc]initWithURL:url];
[_netWorkfetcher startWithCompletionHandler:^(NSData *data) {
NSLog(@"equest URL%@ finish", _netWorkfetcher.url);
_fetcherdData = data;
}];
//end 保留环
//如何打破保留环
NSURL *url2 = [[NSURL alloc]initWithString:@"www.huangshuxian.cn"];
_netWorkfetcher = [[EOCNetworkFetcher alloc]initWithURL:url2];
[_netWorkfetcher startWithCompletionHandler:^(NSData *data) {
NSLog(@"equest URL%@ finish", _netWorkfetcher.url);
_fetcherdData = data;
_netWorkfetcher = nil;
}];
}
@end
要点:
- 如果块所捕获的对象直接或间接地保留了块本身,那么就得当心保留环问题
- 一定要找个适当的时机解除保留环,而不能把责任推给API的调用者
四十一、多用派发队列,少用同步锁
在Objective-C中,如果多个线程要执行同一份代码,那么有时会出现问题,通常需要使用锁来实现某种同步机制。 在GCD出现之前,有两种办法:
一. 采用内置的"同步块"(synchronization block)
-(void)synchronizedMethod{
@synchronized(self){
//Safe
}
}
/*根据给定的对象,自动创建一个锁,并等待块中代码执行完毕。
***可以保证每个对象实例都能不受干扰地运行其synchronizedMethod方法
***然而滥用@synchronized(self)会降低代码效率
*/
二、直接使用NSLock对象
_lock = [NSLock alloc] init];
-(void)synchronizedMethod{
[_lock lock];
//Safe
[_lock unlock];
}
//也可以使用NSRecursiveLock这种递归锁。线程能够多次持有该锁,而不会出现死锁现象(deadlock)
这两种方法都很好,不过也有缺陷。在极端情况下,同步锁会导致死锁,此外,其效率也不见得高。在同一个线程上多次调用获取方法(getter),每次获取到的结果未必相同,在两次访问操作之间,其他线程可能会写入新的属性值。
GCD,它能以更简单、更高效的形式为代码加锁。
串行队列(serial synchronization queue), 任务以FIFO从序列中一个一个执行。一次只调度一个任务,队列中的任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)而且只会开启一条线程。系统提供的最常用的串行队列为Main Dispatch Queue。获取方法为:dispatch_get_main_queue()。
串行同步队列可以代替同步块或锁对象。它将读取操作及写入操作都安排在同一个队列里,即可保证数据同步。其用法如下:
_syncQueue = dispatch_queue_create("com.csesteel.syncQueue", DISPATCH_QUEUE_SERIAL);//第二个参数为DISPATCH_QUEUE_SERIAL或者NULL,则生成串行队列。
-(NSString *)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue,^{
localSomeString = _someString;
});
return localSomeString;
}
-(void)setSomeString:(NSString*)someString{
dispatch_sync(_syncQueue,^{
_someString = someString;
});
}
//此模式的思路是:把设置操作与获取操作都安排在序列化的队列里执行。这样的话,所有针对属性的访问操作就都同步了。
还可以进一步优化。设置方法不一定非得是同步的。设置实例变量所用到的块,并不需要向设置方法返回什么值。所以可以将设置方法改为:
-(void)setSomeString:(NSString*)someString{
dispatch_async(_syncQueue,^{
_someString = someString;
});
}
由原来的dispatch_sync方法改为dispatch_async方法,即将原来的同步派发改成了异步派发。 这提高了执行速度(不等待执行结果返回),而读取操作和写入操作依然会按顺序执行。 但有个坏处是:执行异步派发,需要拷贝块,如果拷贝块所用时间明显超过执行块所花时间,则这种做法比原来更慢。然而,若是派发队列的块要执行更为繁重的任务,那么采用异步派发还是值得考虑的。
多个获取方法可以并发执行,而获取方法与设置方法之间不能并发执行,利用这个特点,我们可以使用并发队列+栅栏(barrier)来实现。
并发队列(concurrent queue),任务以FIFO从序列中移除,然后并发运行,可以按照任何顺序完成。它会自动开启多个线程同时执行任务。
Global Dispatch Queue为系统提供的最常用的并发序列,可用dispatch_get_global_queue(优先级,0)方法获取
_syncQueue = dispatch_queue_create("com.csesteel.syncQueue",DISPATCH_QUEUE_CONCURRENT); //第二个参数为DISPATCH_QUEUE_CONCURRENT,则生成并发队列。
栅栏(barrier):下列函数可以向队列中派发块,将其作为栅栏使用:
void dispatch_barrier_async(dispatch_queue_t queue,dispatch_block block);
void dispatch_barrier_sync(dispatch_queue_t queue,dispatch_block block);
在队列中,栅栏块必须单独执行,不能与其他块并行。并发队列如果发现接下来要处理的块是个栅栏块(barrier block),那么就一直要等当前所有并发块都执行完毕,才会单独执行栅栏块。待栅栏块执行过后,再按正常方式继续向下处理。正是介于此特性,使用栅栏块来实现属性的设置方法。
_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;
});
}
要点:
- 派发队列可用来表述同步语义,这种做法比使用@synchronized块或NSLock对象更简单
- 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程
- 使用同步队列及栅栏块,可以令同步行为更高效
四十二、多用GCD,少用performSelector系类方法
performSelector系列方法 可以推迟执行方法调用,也可以指定运行方法所用的线程,这些功能原来很有用,但是在出现了大中枢派发(GCD)以及块(block)这样的新技术之后,就显得不那么必要了。
SEL selector;
if(/*some condition*/){
selector = @selector(newObject);
}else if(/* some other condition*/){
selector = @selector(copy);
}else{
selector = @selector(someProperty);
}
id ret = [object performSelector:selector];
//如果是前两个选择子之一,那么ret对象应由这段代码来释放,而如果是第三个选择子,则无须释放。
//这容易引起内存泄漏,因为编译器无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法。
performSelector系列方法有局限
-(id)performSelector:(SEL)selector
withObject:(id)object;
-(id)performSelector:(SEL)selector
withObject:(id)objectA
withObject:(id)objectB;
//以上两个方法 由于参数类型是id,所以传入参数必须是对象,且选择子最多能接受两个。再参数不止两个的情况下,没有对应的处理方法
-(void)performSelector:(SEL)selector
withObject:(id)argument
afterDelay:(NSTimeInterval)delay;
-(void)performSelector:(SEL)selector
onThread:(NSThread*)thread
withObject:(id)argument
waitUntilDone:(BOOL)wait;
//具备延后执行功能的那些方法都无法处理带有两个参数的选择子。如果要用这些方法,就得把许多参数都打包到字典,然后在受调用的方法里将其提取出来。这样会增加开销,而且还可能出bug。
优化替代方案如下:
//利用GCD来替代 延后执行任务
[self performSelector:@selector(dosomeThing) withObject:nil afterDelay:5.0f];
//Use dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 *NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
[self dosomeThing];
});
//把任务放在主线程上执行
[self performSelectorOnMainThread:@selector(dosomeThing) withObject:nil waitUntilDone:NO];
dispatch_async(dispatch_get_main_queue(), ^{
[self dosomeThing];
});
/**
* 如果想把任务放在另一个线程上执行,那么最好不要用performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现
*
*/
要点:
- performSelector系列方法在内存管理方面容易有疏失。它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法。容易造成内存泄漏。
- performSelector系列方法所能处理的选择子太过局限了,选择子的返回值类型及发送给方法的参数个数都受到限制。
- 如果想把任务放在另一个线程上执行,那么最好不要用到performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。
四十三、掌握GCD及操作队列的使用时机
在执行后台任务时,GCD并不一定是最佳方式,还有一种技术叫做NSOperationQueue。开发者可以把操作 以NSOperation子类的形式 放在队列中,而这些操作也能够并发执行。
NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
为什么要使用 NSOperation、NSOperationQueue?
- 可添加完成的代码块,在操作完成后执行。
- 添加操作之间的依赖关系,方便的控制执行顺序。
- 设定操作执行的优先级。
- 可以很方便的取消一个操作的执行。
- 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
NSOperation、NSOperationQueue 操作和操作队列
在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念
-
操作(Operation)
- 执行操作的意思,换句话说就是你在线程中执行的那段代码。
- 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
-
操作队列(Operation Queues)
- 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)
- 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行
- NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行
NSOperation 实现多线程的使用步骤分为三步:
- 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
- 创建队列:创建 NSOperationQueue 对象。
- 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中
之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。
NSOperation 和 NSOperationQueue 基本使用
NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作。
使用子类 NSInvocationOperation
使用子类 NSBlockOperation
-
自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作
// 1.创建 NSInvocationOperation 对象 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; // 2.创建 NSBlockOperation 对象 NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程 } }]; //3.使用自定义NSBlockOperation子类对象 YZGOperation *op = [[YSCOperation alloc] init];
NSOperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能
- 主队列
-
凡是添加到主队列中的操作,都会放到主线程中执行(注:不包括操作使用addExecutionBlock:添加的额外操作,额外操作可能在其他线程执行)
// 主队列获取方法 NSOperationQueue *queue = [NSOperationQueue mainQueue];
-
- 自定义队列(非主队列)
添加到这种队列中的操作,就会自动放到子线程中执行。
-
同时包含了:串行、并发功能
// 自定义队列创建方法 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
将操作加入到队列中,方法有两种:
-
- (void)addOperation:(NSOperation *)op;
- 需要先创建操作,再将创建好的操作加入到创建好的队列中去。
-
- (void)addOperationWithBlock:(void (^)(void))block;
- 无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。
NSOperationQueue 控制串行执行、并发执行
由最大并发操作数:maxConcurrentOperationCount控制
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
-
大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
//设置最大并发操作数 queue.maxConcurrentOperationCount = 1; // 串行队列 // queue.maxConcurrentOperationCount = 2; // 并发队列 // queue.maxConcurrentOperationCount = 8; // 并发队列
特别注意的是:
这里 maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。
NSOperation 操作依赖
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。
- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
- @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。
//添加依赖
[op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,op1执行完毕后,再执行op2
NSOperation 优先级
NSOperation 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。
// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0, //默认值
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)
NSOperation、NSOperationQueue 线程安全
线程安全解决方案:可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/ge等等各种方式。
四十四、通过Dispatch Group机制,根据系统资源状况来执行任务
dispatch group是GCD的一项特性,能够把任务分组。调用者可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。
dispatch_group_t dispatch_group_create();
想把任务分组,有两种办法:
//1. 方法1
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
//方法2,必须成对使用否则会造成内存泄漏。如果enter之后没leave,这组任务永远执行不完
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
下面这个函数可用于等待dispatch_group执行完毕:
long dispatch_group_wait(dispatch_group_t group,
dispatch_time_t timeout)
//参数1 要等待的group ,
//参数2 等待时间值 可以为DISPATCH_TIME_FORVER,表示一直等待知道该group执行完
还有另外一个不会造成当前线程阻塞的办法如下:
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block)
//与wait函数略有不同的是:开发者可以向此函数传入块,等dispatch_group执行完毕之后,块block会在特定的线程queue上执行。
四十五、使用dispatch_once来执行只需运行一次的线程安全代码
+(id)sharedInstance{
static EOCClass *sharedInstance = nil;
static dispatch_once_t onceToken; //static的标记
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
使用dispatch_once可以简化代码并且彻底保证线程安全,开发者无须担心加锁或同步。且dispatch_once更加高效。因为它没有使用重量级的同步机制。
要点:
- 经常需要编写"只需执行一次的线程安全代码"。通过GCD的dispatch_once函数,很容易就能实现此功能。
- 标记应该声明在static或global作用域中,这样的话,在把只需执行一次的块传给dispatch_once函数时,传进去的值也是相同的。
四十六、不要使用dispatch_get_current_queue
_syncQueue = dispatch_queue_create("com.yzg.syncQueue",NULL);
-(NSString *)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue,^{
localSomeString = _someString;
});
return localSomeString;
}
上述getter方法是不可重入的。什么叫不可重入?就是说该getter不能再放在_syncQueue中调用,否则会出现互相等待结束,可永远等不到,造成死锁。
/**
* ”队列特有数据“
*
* @return 任意数据以键值对的形式关联到队列里
*/
dispatch_queue_t queueA = dispatch_queue_create("queueA.Jie", NULL);
dispatch_queue_t queueB = dispatch_queue_create("queueB.Zou", NULL);
dispatch_set_target_queue(queueA, queueB);
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA, &queueSpecificValue, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);
//queueA 待设置数据的队列 &queueSpecificValue (void *)queueSpecificValue 键和值 (dispatch_function_t)CFRelease 析构函数(只能带一个指针参数且返回值必须是void)调用CFRelease“参数”以清理旧值
dispatch_sync(queueB, ^{
dispatch_block_t block = ^{NSLog(@"No deadLock");};
CFStringRef retriedValue = dispatch_get_specific(&kQueueSpecific);
if (retriedValue){
block();
}else{
dispatch_sync(queueA, ^{
});
}
});
- dispatch_get_current_queue函数行为常常与开发者所预期的不同。此函数已经废弃,只应做调试使用。
- 由于派发队列是按层级来组织,所以无法单用某个队列对象来描述"当前队列"这个概念
- dispatch_get_current_queue函数用于解决由不可重入的代码所引发的死锁,然而能用此函数解决的问题,通常也能改用"队列特定数据"来解决
-
队列特定数据设置
void dispatch_queue_set_specific(dispatch_queue_t queue,const void *key,void *context,dispatch_function_t destructor);
-