「iOS」——多线程原理总结

底层学习


前言

多线程在iOS的开发中起到了非常重要的作用,是IOS开发必须掌握的一环,现在开始进行一个简单的总结。

基本概念及原理

线程、进程与队列

线程的定义:
  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
  • 进程想要执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被成为主线程UI线程
进程的定义:
  • 进程是指在系统中正在运行的一个应用程序,如微信、支付宝app都是一个进程
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内

通俗地说,可以理解为:进程是线程的容器,而线程用来执行任务。在iOS中是单进程开发,一个进程就是一个app,进程之间是相互独立的,如支付宝、微信、qq等,这些都是属于不同的进程。

线程与进程之间的联系与区别:
  • 地址空间:同一进程线程共享本进程的地址空间,而进程之间则是独立的地址空间
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的
  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉,所以多进程要比多线程健壮
  • 进程切换时,消耗的资源大、效率高.所以设计到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程而不能用进程
  • 线程是处理器调度的基本单位,但进程不是
  • 线程没有地址空间,线程包含在进程地址空间中
线程和runloop的关系
  • runloop与线程是一一对应的 ------ 一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里

  • runloop是来管理线程的 ------ 当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务

  • runloop在第一次获取时被创建,在线程结束时被销毁

    • 对于主线程来说,runloop在程序一启动就默认创建好了

    • 对于子线程来说,runloop是懒加载的 ------ 只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调

影响任务执行速度的因素

以下因素都会对任务的执行速度造成影响:

  • cpu的调度
  • 线程的执行速率
  • 队列情况
  • 任务执行的复杂度
  • 任务的优先级

多线程

多线程生命周期

多线程的生命周期主要分为5部分:新建 - 就绪 - 运行 - 阻塞 - 死亡!

  • 新建:实例化线程对象
  • 就绪:线程对象调用start方法,将线程对象加入可调度线程池等待CPU的调用,即调用start方法,并不会立即执行,进入就绪状态,需要等待一段时间,经CPU调度后才执行,也就是从就绪状态进入运行状态
  • 运行:CPU 负责调度可调度线程池中线程的执行。在线程执行完成之前,其状态可能会在就绪和运行之间来回切换.就绪和运行之间的状态变化由CPU负责,程序员不能干预
  • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)
  • 死亡:正常死亡,即线程执行完毕。非正常死亡,即当满足某个条件后,在线程内部(或者主线程中)终止执行(调用exit方法等退出)

处于运行中的线程拥有一段可以执行的时间(称为时间片):

  • 如果时间片用尽,线程就会进入就绪状态队列
  • 如果时间片没有用尽,且需要开始等待某事件,就会进入阻塞状态队列
  • 等待事件发生后,线程又会重新进入就绪状态队列
  • 每当一个线程离开运行,即执行完毕或者强制退出后,会重新从就绪状态队列选择一个线程继续执行

关于线程的exit和cancel方法:

  • exit:一旦强行终止线程,后续的所有代码都不会执行
  • cancel:取消当前线程,但是不能取消正在执行的线程

线程池的原理

可以看到主要分为以下四步:

  1. 判断核心线程池是否都正在执行任务:
    • 返回NO,创建新的工作线程去执行
    • 返回YES,进行第二步
  2. 判断线程池工作队列是否已经饱满:
    • 返回NO,将任务存储到工作队列,等待CPU调度
    • 返回YES,进入第三步
  3. 判断线程池中的线程是否都处于执行状态
    • 返回NO,安排可调度线程池中空闲的线程去执行任务
    • 返回YES,进入第四步
  4. 交给饱和策略去执行,主要有以下四种:
    • AbortPolicy:直接抛出RejectedExecutionExeception异常来阻止系统正常运行
    • CallerRunsPolicy:将任务回退到调用者
    • DisOldestPolicy:丢掉等待最久的任务
    • DisCardPolicy:直接丢弃任务

iOS中多线程的实现方式

iOS中多线程的实现方式主要有四种:pthread、NSThread、GCD、NSOperation

线程安全问题

当多个线程同时访问一块内存,容易引发数据错乱和数据安全问题,有以下两种解决方案:

  • 互斥锁(即同步锁):@synchronized
  • 自旋锁
互斥锁
  • 保证锁内的代码,同一时间,只有一条线程能够执行!
  • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
  • 加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠
  • 能够加锁的是任意 NSObject 对象,但必须是 NSObject 对象
  • 锁对象必须保证所有线程都能访问
  • 单点加锁时推荐使用 self
自旋锁
  • 自旋锁与互斥锁类似,但它不是通过休眠使线程阻塞,而是在获取锁之前一直处于忙等(即原地打转,称为自旋)阻塞状态

  • 使用场景:锁持有的时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic,本身就有一把自旋锁

  • 加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能

  • atomic 本身就有一把锁(自旋锁)

iOS开发的建议:

  • 所有属性都声明为 nonatomic
  • 尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

对比GCD和NSOperation

GCDNSOperation的关系如下:

  • GCD是面向底层的C语言的API
  • NSOperation是用GCD封装构建的,是GCD的高级抽象

GCD和NSOperation的对比如下:

  • GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构 ------ 写起来更加方便
  • GCD只支持FIFO的队列,而NSOpration可以设置最大并发数、设置优先级、添加依赖关系等调整执行顺序
  • NSOpration甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier任务才能控制执行顺序,较为复杂
  • NSOperation支持KVO(面向对象)可以检测operation是否正在执行、是否结束、是否取消(如果是自定义的NSOperation 子类,需要手动触发KVO通知)

NSThread

NSthread是苹果官方提供面向对象的线程操作技术,是对thread的上层封装,比较偏向于底层。

通过NSThread创建线程的方式主要有以下三种方式:

  • 通过init初始化方式创建
  • 通过detachNewThreadSelector构造器方式创建
  • 通过performSelector...方法创建,主要是用于获取主线程,以及后台线程
objective-c 复制代码
//1、创建
- (void)cjl_createNSThread{
    NSString *threadName1 = @"NSThread1";
    NSString *threadName2 = @"NSThread2";
    NSString *threadName3 = @"NSThread3";
    NSString *threadNameMain = @"NSThreadMain";
    
    //方式一:初始化方式,需要手动启动
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1];
    [thread1 start];
    
    //方式二:构造器方式,自动启动
    [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2];
    
    //方式三:performSelector...方法创建
    [self performSelectorInBackground:@selector(doSomething:) withObject:threadName3];
    
    //方式四
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES];
    
}
- (void)doSomething:(NSObject *)objc{
    NSLog(@"%@ - %@", objc, [NSThread currentThread]);
}

属性

objective-c 复制代码
- thread.isExecuting    //线程是否在执行
- thread.isCancelled    //线程是否被取消
- thread.isFinished     //是否完成
- thread.isMainThread   //是否是主线程
- thread.threadPriority //线程的优先级,取值范围0.0-1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高

NSThread常用的类方法有以下:

  • currentThread:获取当前线程
  • sleep...:阻塞线程
  • exit:退出线程
  • mainThread:获取主线程

GCD

GCD就是Grand Central Dispatch,它是纯 C 语言。

日常开发中,一般为以下形式:

objective-c 复制代码
 dispatch_async( dispatch_queue_create("com.CJL.Queue", NULL), ^{
   NSLog(@"GCD基本使用");
});

GCD核心 主要是由 任务 + 队列 + 函数 构成

objectivec 复制代码
//********GCD基础写法********
//创建任务
dispatch_block_t block = ^{
    NSLog(@"hello GCD");
};

//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);

//将任务添加到队列,并指定函数执行
dispatch_async(queue, block);
  • 使用dispatch_block_t创建任务
  • 使用dispatch_queue_t创建队列
  • 将任务添加到队列,并指定执行任务的函数dispatch_async

注意

这里的任务是指执行操作的意思,在使用dispatch_block_t创建任务时,主要有以下两点说明

  • 任务使用block封装
  • 任务的block没有参数没有返回值

函数

GCD中执行任务的方式有两种,同步执行异步执行,分别对应同步函数dispatch_sync异步函数dispatch_async

  • 同步执行,对应同步函数dispatch_sync
    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程,即不具备开启新线程的能力
    • 在当前线程中执行block任务
  • 异步执行,对应异步函数dispatch_async
    • 不用等待当前语句执行完毕,就可以执行下一条语句
    • 会开启线程执行block任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)
    • 异步是多线程的代名词

综上所述,两种执行方式的主要区别有两点:

  • 是否等待队列的任务执行完毕
  • 是否具备开启新线程的能力

队列

多线程中所说的队列Dispatch Queue)是指执行任务的等待队列,即用来存放任务的队列.队列是一种特殊的线性表,遵循先进先出(FIFO)原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取.每读取一个任务,则动队列中释放一个任务。而队列又分为串行队列并发队列

串行队列 :每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程

并发队列 :一次可以并发执行多个任务,即开启多个线程,并同时执行任务

  • 串行队列:每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程(通俗理解:同一时刻只调度一个任务执行)

    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);创建串行队列

    • 其中的DISPATCH_QUEUE_SERIAL也可以使用NULL表示,这两种均表示 默认的串行队列

objectivec 复制代码
// 串行队列的获取方法
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.CJL.Queue", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_SERIAL);
  • 并发队列:一次可以并发执行多个任务,即开启多个线程,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行)

    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);创建并发队列

    • 注意:并发队列的并发功能只有在异步函数下才有效

objectivec 复制代码
// 并发队列的获取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
主队列 和 全局并发队列
  • 主队列(Main Dispatch Queue):GCD中提供的特殊的串行队列

    • 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建

    • 不会开启线程

    • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

    • 使用dispatch_get_main_queue()获得主队列

    • 通常在返回主线程 更新UI时使用

    objectivec 复制代码
    //主队列的获取方法
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • 全局并发队列(Global Dispatch Queue):GCD提供的默认的并发队列

    • 为了方便程序员的使用,苹果提供了全局队列
    • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
    • 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
      • 第一个参数表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在ios9之后,已经被服务质量(quality-of-service)取代
      • 第二个参数使用0
objectivec 复制代码
  //全局并发队列的获取方法
  dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
  
  //优先级从高到低(对应的服务质量)依次为
  - DISPATCH_QUEUE_PRIORITY_HIGH       -- QOS_CLASS_USER_INITIATED
  - DISPATCH_QUEUE_PRIORITY_DEFAULT    -- QOS_CLASS_DEFAULT
  - DISPATCH_QUEUE_PRIORITY_LOW        -- QOS_CLASS_UTILITY
  - DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND

全局并发队列 + 主队列 配合使用

在日常开发中,全局队列+并发并列一般是这样配合使用的

objectivec 复制代码
//主队列 + 全局并发队列的日常使用
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //执行耗时操作
        dispatch_async(dispatch_get_main_queue(), ^{
            //回到主线程进行UI操作
        });
    });

函数与队列的不同组合

串行队列 + 同步派发

任务一个接一个地在当前线程执行,不会开辟新线程

串行队列 + 异步派发

任务一个接一个地执行,但是会开辟新线程

并发队列 + 同步派发

任务一个接一个地执行,不开辟线程

并发队列 + 异步派发

任务乱序进行 并且会开辟新线程

主队列 + 同步函数

任务互相等待 ,造成死锁

·

为什么这样会造成死锁,这里分析一下原因:

  • 主队列有两个任务,顺序为:NSLog任务 - 同步block

  • 执行NSLog任务后,执行同步Block,会将任务1(即i=1时)加入到主队列,主队列顺序为:NSLog任务 - 同步block - 任务1

  • 任务1的执行需要等待同步block执行完毕才会执行,而同步block的执行需要等待任务1执行完毕,所以就造成了任务互相等待的情况,即造成死锁崩溃

死锁

  • 主线程因为同步函数的原因等着先执行任务
  • 主队列等着主线程的任务执行完毕再执行自己的任务
  • 主队列和主线程相互等待会造成死锁
主队列 + 异步派发

主队列是一个特殊的串行队列 ,它虽然是串行队列,但是其异步派发不会开辟新线程 ,而是将任务安排到主线程的下一个运行循环(Run Loop)周期执行

全局并发队列 + 同步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟新线程

全局并发队列 + 异步函数

【任务乱序执行】:任务乱序执行,会开辟新线程

总结
函数\队列 串行队列 并发队列 主队列 全局并发队列
同步函数 顺序执行,不开辟线程 顺序执行,不开辟线程 死锁 顺序执行,不开辟线程
异步函数 顺序执行,开辟线程 乱序执行,开辟线程 顺序执行,不开辟线程 乱序执行,开辟线程

dispatch_after

dispatch_after表示在队列中的block延迟执行,确切地说是延迟将block加入到队列

objectivec 复制代码
- (void)cjl_testAfter{
    /*
     dispatch_after表示在某队列中的block延迟执行
     应用场景:在主队列上延迟执行一项任务,如viewDidload之后延迟1s,提示一个alertview(是延迟加入到队列,而不是延迟执行)
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"2s后输出");
    });
   
}

dispatch_once

dispatch_once可以保证在app运行期间,block中的代码只执行一次,可以用来创建单例

objectivec 复制代码
- (void)cjl_testOnce{
    /*
     dispatch_once保证在App运行期间,block中的代码只执行一次
     应用场景:单例、method-Swizzling
     */
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //创建单例、method swizzled或其他任务
        NSLog(@"创建单例");
    });
}

dispatch_apply

dispatch_apply将指定的block追加到指定的队列中重复执行,并等到全部的处理执行结束(相当于线程安全的for循环)

应用场景:在拉取网络数据后提前计算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性

objectivec 复制代码
- (void)cjl_testApply{
    /*
     dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束------相当于线程安全的for循环

     应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性
     - 添加到串行队列中------按序执行
     - 添加到主队列中------死锁
     - 添加到并发队列中------乱序执行
     - 添加到全局队列中------乱序执行
     */
    
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);
    NSLog(@"dispatch_apply前");
    /**
         param1:重复次数
         param2:追加的队列
         param3:执行任务
         */
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"dispatch_apply 的线程 %zu - %@", index, [NSThread currentThread]);
    });
    NSLog(@"dispatch_apply后");
}

dispatch_group_t

dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间

dispatch_group_notify

  • 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

dispatch_group_wait

  • 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1

  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。

  • 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。

应用场景:多个接口请求之后刷新页面

dispatch_group_async + dispatch_group_notify

dispatch_group_notifydispatch_group_async执行结束之后会受收到通知

objectivec 复制代码
- (void)cjl_testGroup1{
    /*
     dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间

     应用场景:多个接口请求之后刷新页面
     */
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"请求一完成");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"请求二完成");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新页面");
    });
  
  //或者用下面这个:
// 等待所有任务完成
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));
if (dispatch_group_wait(group, timeout) == 0) {
    // 所有数据获取完成,进行最终处理
    NSLog(@"All data fetched!");
} else {
    // 超时,可以执行一些错误处理逻辑
    NSLog(@"Timeout waiting for data");
}

}

dispatch_group_notify 不同,dispatch_group_wait 是一个同步操作,会阻塞当前线程,直到所有任务完成或者超时。在这里,我们设置了一个 10 秒的超时时间,如果在 10 秒内所有任务都完成,则打印 "All data fetched!"。如果超时,则打印 "Timeout waiting for data"。

dispatch_group_enter + dispatch_group_leave + dispatch_group_notify

dispatch_group_enterdispatch_group_leave成对出现,使进出组的逻辑更加清晰

objectivec 复制代码
- (void)cjl_testGroup2{
    /*
     dispatch_group_enter和dispatch_group_leave成对出现,使进出组的逻辑更加清晰
     */
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求二完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}

在此基础上还可以使用 dispatch_group_wait

objectivec 复制代码
- (void)cjl_testGroup3{
    /*
     long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

     group:需要等待的调度组
     timeout:等待的超时时间(即等多久)
        - 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕
        - 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕


     返回值:为long类型
        - 返回值为0------在指定时间内调度组完成了任务
        - 返回值不为0------在指定时间内调度组没有按时完成任务

     */
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求二完成");
        dispatch_group_leave(group);
    });
    
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC));
    NSLog(@"timeout = %ld", timeout);
    if (timeout == 0) {
        NSLog(@"按时完成任务");
    }else{
        NSLog(@"超时");
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}

NSOperation

NSOperation是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue来实现多线程

NSOperatino实现多线程的步骤如下:

  • 1、创建任务:先将需要执行的操作封装到NSOperation对象中。
  • 2、创建队列:创建NSOperationQueue。
  • 3、将任务加入到队列中:将NSOperation对象添加到NSOperationQueue中。
objectivec 复制代码
//基本使用
- (void)cjl_testBaseNSOperation{
    //处理事务
    NSInvocationOperation *op =  [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation::) object:@"CJL"];
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //操作加入队列
    [queue addOperation:op];
    
}
- (void)handleInvocation:(id)operation{
    NSLog(@"%@ - %@", operation, [NSThread currentThread]);
}

需要注意的是,NSOperation是个抽象类,实际运用时中需要使用它的子类,有三种方式:

  • 1、使用子类NSInvocationOperation
objectivec 复制代码
//直接处理事务,不添加隐性队列
- (void)cjl_createNSOperation{
    //创建NSInvocationOperation对象并关联方法,之后start。
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"CJL"];
    
    [invocationOperation start];
}
  • 2、使用子类NSBlockOperation
objectivec 复制代码
- (void)cjl_testNSBlockOperationExecution{
    //通过addExecutionBlock这个方法可以让NSBlockOperation实现多线程。
    //NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的。
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"main task = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
            NSLog(@"task1 = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
            NSLog(@"task2 = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
            NSLog(@"task3 = >currentThread: %@", [NSThread currentThread]);
    }];
    
    [blockOperation start];
}
  • 3、定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
objectivec 复制代码
//*********自定义继承自NSOperation的子类*********
@interface CJLOperation : NSOperation
@end

@implementation CJLOperation
- (void)main{
    for (int i = 0; i < 3; i++) {
        NSLog(@"NSOperation的子类:%@",[NSThread currentThread]);
    }
}
@end

//*********使用*********
- (void)cjl_testCJLOperation{
    //运用继承自NSOperation的子类 首先我们定义一个继承自NSOperation的类,然后重写它的main方法。
    CJLOperation *operation = [[CJLOperation alloc] init];
    [operation start];
}

NSOperationQueue

NSOperationQueue添加事务

NSOperationQueue有两种队列:主队列其他队列。其他队列包含了 串行和并发

  • 主队列:主队列上的任务是在主线程执行的。
  • 其他队列(非主队列):加入到'非队列'中的任务默认就是并发,开启多线程。
objectivec 复制代码
- (void)cjl_testNSOperationQueue{
    /*
     NSInvocationOperation和NSBlockOperation两者的区别在于:
     - 前者类似target形式
     - 后者类似block形式------函数式编程,业务逻辑代码可读性更高
     
     NSOperationQueue是异步执行的,所以任务一、任务二的完成顺序不确定
     */
    // 初始化添加事务
    NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务1------------%@",[NSThread currentThread]);
    }];
    // 添加事务
    [bo addExecutionBlock:^{
        NSLog(@"任务2------------%@",[NSThread currentThread]);
    }];
    // 回调监听
    bo.completionBlock = ^{
        NSLog(@"完成了!!!");
    };
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:bo];
    NSLog(@"事务添加进了NSOperationQueue");
}

设置执行顺序

objectivec 复制代码
//执行顺序
- (void)cjl_testQueueSequence{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        for (int i = 0; i < 5; i++) {
            [queue addOperationWithBlock:^{
                NSLog(@"%@---%d", [NSThread currentThread], i);
            }];
        }
}

设置优先级

objectivec 复制代码
- (void)cjl_testOperationQuality{
    /*
     NSOperation设置优先级只会让CPU有更高的几率调用,不是说设置高就一定全部先完成
     - 不使用sleep------高优先级的任务一先于低优先级的任务二
     - 使用sleep进行延时------高优先级的任务一慢于低优先级的任务二
     */
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            //sleep(1);
            NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置最高优先级
    bo1.qualityOfService = NSQualityOfServiceUserInteractive;
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置最低优先级
    bo2.qualityOfService = NSQualityOfServiceBackground;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:bo1];
    [queue addOperation:bo2];

}

设置并发数

objectivec 复制代码
//设置并发数
- (void)cjl_testOperationMaxCount{
    /*
     在GCD中只能使用信号量来设置并发数
     而NSOperation轻易就能设置并发数
     通过设置maxConcurrentOperationCount来控制单次出队列去执行的任务数
     */
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"Felix";
    queue.maxConcurrentOperationCount = 2;
    
    for (int i = 0; i < 5; i++) {
        [queue addOperationWithBlock:^{ // 一个任务
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}

添加依赖(先后顺序)

objectivec 复制代码
//添加依赖
- (void)cjl_testOperationDependency{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"请求token");
    }];
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着token,请求数据1");
    }];
    
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着数据1,请求数据2");
    }];
    
    [bo2 addDependency:bo1];
    [bo3 addDependency:bo2];
    
    [queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
    
    NSLog(@"执行完了?我要干其他事");
}

线程间通讯

objectivec 复制代码
//线程间通讯
- (void)cjl_testOperationNoti{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"Felix";
    [queue addOperationWithBlock:^{
        NSLog(@"请求网络%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
        
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
        }];
    }];

}