iOS开发多线程那些事儿

iOS中的多线程


iOS中多线程实现的多种技术方案:

技术方案 简介 语言 线程生命周期 使用频率
Pthread 一套通用的多线程API;适用于Unix\Linux\Windows等系统;跨平台,可移植;使用难度大 C 程序员管理 几乎不用
NSThread 使用更加面向对象;简单易用,可直接操作线程对象 OC 程序员管理 偶尔使用
GCD 旨在替代NSthread等线程技术;充分利用设备的多核 C 自动管理 经常使用
NSOperation 基于GCD;使用更加面向对象 OC 自动管理 经常使用

多线程的两组基本概念

  • 串行(Serial):在固定时间内只能执行单个任务。例如主线程,只负责 UI 显示。
    https://images2015.cnblogs.com/blog/890649/201602/890649-20160203192610444-1626760503.gif

  • 并发(Concurrent):在固定时间内可以执行多个任务。任务可能是以在单核 CPU 上分时(通过在任务间不断高速切换)的形式同时运行,也可能是在多核 CPU 上以真正的并行(Parallel)方式来运行
    https://images2015.cnblogs.com/blog/890649/201602/890649-20160203192629804-1503188519.gif

  • 同步(Sync):会把当前的任务加入到队列中,除非该任务执行完成,线程才会返回继续运行,也就是说同步会阻塞线程。任务在执行和结束一定遵循先后顺序,即先执行的任务一定先结束。

  • 异步(Async):会把当前的任务加入到队列中,但它会立刻返回,无需等任务执行完成,也就是说异步不会阻塞线程。任务在执行和结束不遵循先后顺序。可能先执行的任务先结束,也可能后执行的任务先结束。

GCD的介绍


- 基本概念

Applications need to be rewritten to take full advantage of all of these cores, but Apple's new operating system has a new technology that they are calling Grand Central Dispatch.

要想充分利用这些多核芯片,需要重新编写应用程序,但苹果新版操作系统有一个新的技术,他们称之为Grand Central Dispatch(简称GCD技术)。

任务:就是执行操作的意思,即在线程中执行的那段代码,在GCD中是放在block里面的。执行任务有两种方式,分别是:
  • 同步执行(sync):添加任务到指定队列中,等待前面的任务完成之后再开始执行,不具备开启新线程的能力
  • 异步执行(async):添加任务到指定队列中,不用等待就继续执行,具备开启新线程的能力(并不一定开启新的线程)
队列:指的是执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)原则。
队列.png

GCD中队列大致可分两种:串行队列和并发队列,主要区别在于执行顺序和开启线程数不同。

  • 串行队列(Serial Dispatch Queue):每次只有一个任务被执行,任务一个接一个得执行,只开启一条线程。主线程 main Queue 是一种特殊的串行队列

    串行.png

  • 并发队列(Concurrent Dispatch Queue):可以多个任务并发执行,可开启多条线程(实质上是在快速来回切换执行的任务,一般说成是多个任务同时执行)。并发队列的并发功能只有在异步(dispatch_async)函数下才会有效。

    并发.png

- 基本使用步骤

  1. 创建队列;
    /**
     Description 创建串行队列
     "Liuxiaopang.GCDTips" 队列的唯一标志符,用于DEBUG,可为空
     DISPATCH_QUEUE_SERIAL 用于识别是串行还是并行
     */
    dispatch_queue_t serialQueue = dispatch_queue_create("Liuxiaopang.GCDTips", DISPATCH_QUEUE_SERIAL);
    /** 后面标注的NULL也是串行队列 */
    dispatch_queue_t nullQueue = dispatch_queue_create("nullQueue", NULL);

    /**
     Description 创建并行队列
     "Liuxiaopang.GCDTips" 队列的唯一标志符,用于DEBUG,可为空
     DISPATCH_QUEUE_SERIAL 用于识别是串行还是并行
     */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("Liuxiaopang.GCDTips", DISPATCH_QUEUE_CONCURRENT);
    
    /**
     Description 获取主队列————特殊的串行队列
     */
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    /**
     Description 获取全局并发队列————特殊的并行队列
     DISPATCH_QUEUE_PRIORITY_HIGH
     DISPATCH_QUEUE_PRIORITY_DEFAULT
     DISPATCH_QUEUE_PRIORITY_LOW
     DISPATCH_QUEUE_PRIORITY_BACKGROUND 队列优先级从高到底
     0 暂时未用到的参数,为以后备用
     */
    dispatch_queue_t gloabalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

优先级高的队列里的任务会先执行,之后才执行优先级低的队列中的任务(需要注意:十次里面可能有九次都是等待优先级高的队列里的任务全部执行完毕才开始执行优先级低的队列中的任务,但是也会出现优先级高的队列中的任务没有完全执行完成就开始执行优先级低的队列中的任务)
亲测如下(测试很多次才可能出现一次):highGlobalQueue中i=9的那次打印还没执行,lowGlobalQueue中i=0的那次打印就执行了

  1. 将待执行的任务追加到队列中;
 /**
     Description 向queue中添加同步操作block
     queue 队列
     block 操作任务
     */
dispatch_sync(dispatch_queue_t  _Nonnull queue, ^(void)block)
 /** Description 向queue中添加同步操作block */
dispatch_async(dispatch_queue_t  _Nonnull queue, ^(void)block)
串行、并发与同步、异步、主线程的六种组合
  • 同步执行+并发队列:没有开启新线程,串行执行任务
  • 异步执行+并发队列:有开启新线程,并发执行任务
  • 同步执行+串行队列:没有开启新线程,串行执行任务
  • 异步执行+串行队列:有开启新线程(1条),串行执行任务
  • 同步执行+主队列:死锁卡住不执行,这是因为在主线程中执行syncMainQueue方法,相当于把syncMainQueue任务放到了主线程的队列中。而同步执行会等待当前队列中的任务执行完毕,才会接着执行。那么当把某任务(下图中block内部操作)追加到主队列中,此任务(下图中block内部操作)就在等待主线程处理完syncMainQueue任务。而syncMainQueue任务需要等待任务(下图中block内部操作)执行完毕,才能接着执行。那么,现在的情况就是syncMainQueue任务和任务都在等对方执行完毕。这样大家互相等待,所以就卡住了。
  • 异步执行+主队列:没有开启新线程,串行执行任务

- 死锁

  • 在 main queue中进行同步操作,是一定会构成死锁。
  • 同一个串行队列中进行异步、同步嵌套。这里会构成死锁。
  • 同一个串行队列中进行同步、同步嵌套。这里会构成死锁。
- 死锁总结

有两个任务:任务A是任务B的一部分,任务A需要等任务B执行完毕才能开始执行,而任务A作为任务B的一部分,B等A执行完毕才算全部执行完毕,互相等待,然后就等到死

- 常见用法

  • dispatch_apply

该函数指定次数将指定的Block追加到指定的Queue中,并等待全部处理执行结束

第一个参数是执行的次数
第二个参数是任务加入的队列
第三个block是执行的任务,需传入一个次数的参数

dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        NSLog(@"dispatch_apply--%@--%ld",[NSThread currentThread], index);
    });
  • dispatch_barrier

GCD栅栏函数:在并发队列中将此代码插入的地方上下隔开,如果栅栏一样,两部分不影响。只有上边的并发队列都执行结束之后,下边的并发队列才能够执行。
栅栏函数

如下例子中:指定在任务1、2(无序)执行之后,执行任务barrier,然后执行任务3、4(无序)
特点:指定在任务1、2、3、4是在同一个队列中执行才有效

dispatch_barrier_async与dispatch_barrier_sync区别:

dispatch_barrier_sync代码后边的任务直到dispatch_barrier_sync执行完才能被追加到队列中;
dispatch_barrier_async不用代码执行完,后边的任务也会被追加到队列中。
代码上的体现就是dispatch_barrier_sync后边的代码不会执行,dispatch_barrier_async后边的代码会执行,但是Block不会被执行。

    dispatch_queue_t queue = dispatch_queue_create("testDispatch_barrier_async", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier-----%@",[NSThread currentThread]);
    });
    /*
    dispatch_barrier_sync(queue, ^{
        NSLog(@"barrier-----%@",[NSThread currentThread]);
    });
     */
    
    dispatch_async(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
    NSLog(@"the end");
  • dispatch_group

队列组:可以监听不同队列中的多个任务执行结束之后,进行相应的操作,以下几种法,效果是一样的
  1. dispatch_group_notify拦截通知,在指定任务1、2、3(无序)都执行完成之后,执行相应的操作
    特点:可以是监听多个队列下的任务都执行完毕
- (void)testDispatch_group {
    //创建队列组
    dispatch_group_t group = dispatch_group_create();
    //创建并发队列
    dispatch_queue_t firstQueue = dispatch_queue_create("firstQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t secondQueue = dispatch_queue_create("secondQueue", DISPATCH_QUEUE_CONCURRENT);

    //封装任务,添加到队列并监听任务的执行情况
    dispatch_group_async(group, firstQueue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, firstQueue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, secondQueue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, secondQueue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
    dispatch_group_async_f(group, secondQueue, @"testText", functionWotk);
    
    /*
    拦截通知,监听到所有所有任务执行完毕,执行相应的操作
    下面的group指的是在哪个队列组,而firstQueue指的是block任务在哪个对列中执行
    dispatch_group_notify 内部是异步执行
     */
    dispatch_group_notify(group, firstQueue, ^{
        NSLog(@"the notify");
    });
    
    NSLog(@"the end");
}

void functionWotk (void * text) {
    NSLog(@"dispatch_group_async_f-----%@-----%@",[NSThread currentThread],text);
}
  1. dispatch_group_wait 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
/**
 * 队列组 dispatch_group_wait
 */
- (void)testDispatch_groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务1
        for (int i = 0; i < 3; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务2
        for (int i = 0; i < 3; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}
  1. dispatch_group_enter任务追加到group, dispatch_group_leave任务离开 group
- (void)testDispatch_groupEnterAndLeave {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("testDispatch_groupEnterAndLeave", DISPATCH_QUEUE_CONCURRENT);
    
    /*
     在该方法后面的任务会被队列组监听
     dispatch_group_enter与dispatch_group_leave成对使用,对应一进一出
     */
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
        //监听到该任务执行完毕
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"the notify");
    });
}
  • dispatch_semaphore

根据信号量实现多线程同步机制,用来管理对资源的并发访问

dispatch_semaphore 有以下三个函数
dispatch_semaphore_t dispatch_semaphore_create(1);// 创建信号量,参数:信号量的初值,如果不大于0则会返回NULL

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);// 等待降低信号量,接收一个信号和时间值(多为DISPATCH_TIME_FOREVER);若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行

long dispatch_semaphore_signal(dispatch_semaphore_t dsema);// 提高信号量, 使信号量加1并返回

下面提供的是一个出售火车票的场景用例

/**
 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)testTicketSale {
    NSLog(@"semaphore---begin");
    
    __block NSInteger ticketSurplusCount = 50;
    dispatch_semaphore_t semaphoreLock = dispatch_semaphore_create(1);

    // queue1 代表火车票售卖窗口1
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表火车票售卖窗2
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue1, ^{
        while (1) {
            dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);// 相当于加锁
            
            if (ticketSurplusCount > 0) {//如果还有票,继续售卖
                ticketSurplusCount--;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:queue1",ticketSurplusCount]);
                [NSThread sleepForTimeInterval:0.2];
                dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
            } else { //如果已卖完,关闭售票窗口
                NSLog(@"所有火车票均已售完");
                dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
                break;
            }
        }
    });
    
    dispatch_async(queue2, ^{
        while (1) {
            dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);// 相当于加锁
            
            if (ticketSurplusCount > 0) {//如果还有票,继续售卖
                ticketSurplusCount--;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:queue2",ticketSurplusCount]);
                [NSThread sleepForTimeInterval:0.2];
                dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
            } else { //如果已卖完,关闭售票窗口
                NSLog(@"所有火车票均已售完");
                dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
                break;
            }
        }
    });
    //当线程1执行到dispatch_semaphore_wait这一行时,semaphore的信号量为1,所以使信号量-1变为0,并且线程1继续往下执行;如果当在线程1售票操作还没执行完的时候,又有线程2来访问,执行dispatch_semaphore_wait时由于此时信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会一直阻塞线程2(此时线程2处于等待状态),直到线程1执行完售票操作并且执行完dispatch_semaphore_signal使信号量为1后,线程2才能解除阻塞继续住下执行。以上可以保证同时只有一个线程执行售票操作这部分block中的代码
}
  • dispatch_once()

以线程安全的方式执行且仅执行其代码块一次。试图访问临界区(即传递给 dispatch_once 的代码)的不同的线程会在临界区已有一个线程的情况下被阻塞,直到临界区完成为止。

  • dispatch_source_t

/**
     * 创建定时器
     * 第一个参数 dispatch源可处理的事件
     * 第二个参数 可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID
     * 第三个参数 可以理解为描述,提供更详细的描述,让它知道具体要监听什么
     * 第四个参数 自定义源需要的一个队列,用来处理所有的响应句柄(block)
     */
    dispatch_source_create(dispatch_source_type_t  _Nonnull type, uintptr_t handle, unsigned long mask, dispatch_queue_t  _Nullable queue);
    /**
     * 设置定时器
     * 第一个参数 分派源
     * 第二个参数 数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和 dispatch_walltime 函数来创建它们。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
     * 第三个参数 间隔时间
     * 第四个参数 计时器触发的精准程度
     */
    dispatch_source_set_timer(dispatch_source_t  _Nonnull source, dispatch_time_t start, uint64_t interval, uint64_t leeway);


NSOperation的介绍


1、 什么是NSOperation

NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。系统已经给我们封装了NSBlockOperation和NSInvocationOperation(swift中已经被弃用)这两个实体类。使用起来也非常简单,不过我们更多的使用是自己继承并定制自己的操作。

1.1、 基本概念
1.1.1、操作(Operation):

执行操作的意思,换句话说就是你在线程中执行的那段代码。
在 GCD 中是放在 block 中的。** NSOperation是个抽象类,并不具备封装操作的能力**,必须NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。

1.1.2 操作队列(Operation Queues):

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

2、 我们为什么使用NSOperation

在iOS开发中,为了提升用户体验,我们通常会将操作耗时的操作放在主线程之外的线程进行处理。对于正常的简单操作,我们更多的是选择代码更少的GCD,让我们专注于自己的业务逻辑开发。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖

3、 在实际开发中如何使用NSOperation

3.1、 NSBlockOperation
/**
 * 使用子类 NSBlockOperation
 */
- (void)useBlockOperation {
    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:1]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.调用 start 方法开始执行操作
    [op start];
}

/**
 * 使用子类 NSBlockOperation
 * 调用方法 AddExecutionBlock:
 */
- (void)useBlockOperationAddExecutionBlock {

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.添加额外的操作
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"6---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"7---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"8---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.调用 start 方法开始执行操作
    [op start];
}

一般情况下,如果一个 NSBlockOperation 对象封装了多个操作。NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。

3.2、 自定义NSOperation
#import "LZ_Operation.h"

@implementation LZ_Operation
// 重写main方法,即为任务操作
- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"LZ_Operation---%@", [NSThread currentThread]);
        }
    }
}
@end
3.3、 NSOperation的基本使用
**
 * 使用自定义继承自 NSOperation 的子类
 */
- (void)useCustomOperation {
    // 1.创建 YSCOperation 对象
    LZ_Operation *op = [[LZ_Operation alloc] init];
    // 2.调用 start 方法开始执行操作
    [op start];
}
3.4、 NSOperation实现线程间依赖和监听
//任务依赖:依赖关系可以跨队列
- (void)testDependency {
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    
    op3.completionBlock = ^{
        NSLog(@"op3---已经执行完毕,%@", [NSThread currentThread]); // 打印当前线程
    };
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    
    // 设置依赖:4->3->2->1,不能设置循环依赖
    [op4 addDependency:op3];
    [op3 addDependency:op2];
    [op3 addDependency:op1];
    
    [queue1 addOperation:op1];
    [queue1 addOperation:op2];
    [queue1 addOperation:op3];
    [queue2 addOperation:op4];
}
3.5、 NSOperation实现线程间通信
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *downOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:@"http://b163.photo.store.qq.com/psb?/V12zzMqq2GjK4o/sx1drNtYguEi2ADOolsIhkD.whY4ar3QV74jC.PpcG4!/b/dGuRL2FXBwAA&bo=WAIgAwAAAAABB1k!&rf=viewer_4"];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:imageData];
        
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
            NSLog(@"刷新UI-----%@",[NSThread currentThread]);
        }];
    }];
    //KVO
    [downOperation addObserver:self forKeyPath:@"finished" options:NSKeyValueObservingOptionNew context:nil];
    [queue addOperation:downOperation];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  NSLog(@"\nkeyPath======%@,\nobject======%@,\nchange======%@",keyPath,object,change);
}

推荐几篇优秀的文章
并发编程:API 及挑战
深入浅出 iOS 并发编程

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