块与大中枢派发

多线程编程的核心就是"块"(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;

要点:

  1. 以typedef重新定义块类型,可令块变量用起来更加简单
  2. 定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型冲突
  3. 不妨为同一个块签名定义多个类型别名。如果要重构的代码使用了块类型的某个别名,那么只需修改相应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回调方法里根据传入的获取器参数来切换。

要点:

  1. 在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明
  2. 在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler块来实现,则可直接将块与相关对象放在一起
  3. 设计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

要点:

  1. 如果块所捕获的对象直接或间接地保留了块本身,那么就得当心保留环问题
  2. 一定要找个适当的时机解除保留环,而不能把责任推给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;
    });
}

要点:

  1. 派发队列可用来表述同步语义,这种做法比使用@synchronized块或NSLock对象更简单
  2. 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程
  3. 使用同步队列及栅栏块,可以令同步行为更高效

四十二、多用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系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现
 *
 */

要点:

  1. performSelector系列方法在内存管理方面容易有疏失。它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法。容易造成内存泄漏。
  2. performSelector系列方法所能处理的选择子太过局限了,选择子的返回值类型及发送给方法的参数个数都受到限制。
  3. 如果想把任务放在另一个线程上执行,那么最好不要用到performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。

四十三、掌握GCD及操作队列的使用时机

在执行后台任务时,GCD并不一定是最佳方式,还有一种技术叫做NSOperationQueue。开发者可以把操作 以NSOperation子类的形式 放在队列中,而这些操作也能够并发执行。

NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。

为什么要使用 NSOperation、NSOperationQueue?

  1. 可添加完成的代码块,在操作完成后执行。
  2. 添加操作之间的依赖关系,方便的控制执行顺序。
  3. 设定操作执行的优先级。
  4. 可以很方便的取消一个操作的执行。
  5. 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

NSOperation、NSOperationQueue 操作和操作队列
在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念

  • 操作(Operation)

    • 执行操作的意思,换句话说就是你在线程中执行的那段代码。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
  • 操作队列(Operation Queues)

    • 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)
    • 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行
    • NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行

NSOperation 实现多线程的使用步骤分为三步:

  1. 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
  2. 创建队列:创建 NSOperationQueue 对象。
  3. 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中

之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。

NSOperation 和 NSOperationQueue 基本使用
NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作。

  1. 使用子类 NSInvocationOperation

  2. 使用子类 NSBlockOperation

  3. 自定义继承自 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更加高效。因为它没有使用重量级的同步机制。

要点:

  1. 经常需要编写"只需执行一次的线程安全代码"。通过GCD的dispatch_once函数,很容易就能实现此功能。
  2. 标记应该声明在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, ^{
        
    });
    }
});
  1. dispatch_get_current_queue函数行为常常与开发者所预期的不同。此函数已经废弃,只应做调试使用。
  2. 由于派发队列是按层级来组织,所以无法单用某个队列对象来描述"当前队列"这个概念
  3. dispatch_get_current_queue函数用于解决由不可重入的代码所引发的死锁,然而能用此函数解决的问题,通常也能改用"队列特定数据"来解决
    • 队列特定数据设置

        void dispatch_queue_set_specific(dispatch_queue_t queue,const void *key,void *context,dispatch_function_t destructor);
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容