iOS多线程

08 - OC多线程之认识和使用

2021-10-31  本文已影响0人  iOS之文一

OC底层原理探索文档汇总

主要内容:

1、线程认识
2、NSThread认识
3、GCD的认识和使用(重点在于队列和任务)
4、NSOperation的认识和使用

1、基本概念

进程: 在内存中正在运行的程序就是进程,进程是系统进行资源调度分配的一个独立单位,进程有独立性、并发性、动态性。
线程: 线程是进程的执行单元,是一个独立的、并发的顺序执行流,进程所有的任务都在线程中执行。

一个程序后台至少有一个进程,一个进程里可以包含多个线程,至少要包含一个线程。

多线程: 在一个进程中可以开辟多条线程,多条线程可以并发执行不同的任务,多条线程并发执行,其实是CPU快速的在多条线程之间调度,CPU的执行很快,调度的时间足够快就会造成多线程同时执行的假象。

优点:

缺点:

主线程: 一个iOS程序运行后,会默认开启一个线程,这个线程就是主线程,也叫UI线程
作用:

注:一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受到主线程的影响

进程和线程的关系 :线程是进程的执行单元,在进程中开辟的独立、并发的顺序执行流,进程所有的任务都在线程中执行。

线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与附进程的其他线程共享该进程所拥有的全部资源。
因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,确保线程不会妨碍同一进程里的其他线程。

Runloop和线程的关系

线程生命周期:

线程生命周期.png

2、NSThread

NSthread是苹果官方提供面向对象的线程操作技术,是对thread的上层封装,比较偏向于底层。简单方便,可以直接操作线程对象,使用频率较少。

介绍

2.1 NSThread的创建

共有三种方式创建:

//1、创建
- (void)wy_createNSThreadTest{
    NSString *threadName1 = @"NSThread1";
    NSString *threadName2 = @"NSThread2";
    NSString *threadName3 = @"NSThread3";
    NSString *threadNameMain = @"NSThreadMain";
    
    //方式一:初始化方式,需要手动启动
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1];
    thread1.name = @"thread1";
    [thread1 start];
    
    //方式二:构造器方式,自动启动
    [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2];
    
    //方式三:performSelector...方法创建子线程
    [self performSelectorInBackground:@selector(doSomething:) withObject:threadName3];
    
    //方式四:performSelector...方法创建主线程
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES];
    
}

注意:selector方法最多只能接收一个参数,写的objcet就是传的参数

2.2 常用API

属性:

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

说明:
threadPropority已经被qualityOfService服务质量取代了,从上到下,数值越大,优先级越高

服务质量.png

注意:优先级高并不代表执行效率高,仅仅说明被执行的可能性更高

方法:

- (void)wy_NSThreadClassMethod{
    //当前线程
    [NSThread currentThread];
    // 如果number=1,则表示在主线程,否则是子线程
    NSLog(@"%@", [NSThread currentThread]);
    
    //阻塞休眠
    [NSThread sleepForTimeInterval:2];//休眠多久
    [NSThread sleepUntilDate:[NSDate date]];//休眠到指定时间
    
    //其他
    [NSThread exit];//退出线程
    [NSThread isMainThread];//判断当前线程是否为主线程
    [NSThread isMultiThreaded];//判断当前线程是否是多线程
    NSThread *mainThread = [NSThread mainThread];//主线程的对象
    NSLog(@"%@", mainThread);
}

说明:

简单案例:
在其他线程终止另一个线程
比如在UI线程中终止一个子线程,可以给子线程发送一个信号,子线程接收到后调用exit进行退出
在UI线程中调用cancel后,线程的isCancelled方法将会返回NO

2.3 线程同步

多个线程操作同一个代码,并且修改同一个资源,就会造成线程安全问题,这里就需要线程同步
线程同步就是通过加锁来实现的,通过锁可以保证在任意时刻,只有一个线程在操作共享数据,从而保证了线程的安全
具体锁的认识后面会详细分析。

2.3 线程通信

多个线程操作不同的代码,去修改同一个资源,这里的代码块不是同一个代码,所以就不能通过线程同步来实现。

注意与线程同步的区别,这里是多个线程操作不同代码,而不是相同代码。

这里使用到了NSCondition,他既可以实现线程同步,也可以实现线程通信。具体锁的使用后面会详细分析,这里先简单说明

等待唤醒机制:

当一个线程在执行时,将其他有相同锁的线程设置为等待状态,当自己执行结束,把其他线程设置为唤醒状态,这样就可以实现线程安全了。

等待
API:wait/waitUnitlData
将当前线程设置为等待状态,知道其他线程调用signal或broadcast来唤醒,如果是waitUnitdate则等到了过了某个时间点,会自动唤醒

唤醒
API:signal

API:boadcaset

注意:

  • 主线程不能直接中断子线程,需要通过设置isCancelled来判断
    • 主线程中让子线程调用[thread cancel]方法即可让子线程的isCancelled设置为NO
    • 在子线程中加判断,如果isCancelled为NO,则用[NSThread exit]来中断自己
  • 线程优先级只是代表他抢夺CPU的概率大,不一定会真的能抢到
  • 子线程执行完后如果要更新数据,必须进入到主线程,不然出现安全问题
  • 当睡眠时间到了之后,或者获取到同步锁后,并不会立即运行,而是先处于就绪状态,等CPU调度到这个线程才会运行

3、GCD

全称是Grand Central Dispatch,中央调度中心,我们将任务添加到队列,并且指定执⾏任务的函数,GCD会帮我们将任务取出并放到线程中执行

优点:

3.1 队列和任务的认识

这里仅做简单认识,更详细的内容可以查看OC多线程之队列和任务的认识

3.1.1 队列

队列就是管理待执行任务的等待队列,用来调度任务给线程执行,符合先进先出原则
主队列是指主线程的队列,是一个串行队列,在主线程去执行其实就是放入了主队列
全局并发队列,可以供整个应用使用,没有特别之处,只是一个系统创建好的并发队列,我们可以直接使用

并发和串行决定了任务执行的顺序,并发是多个任务并发执行,串行是指任务顺序执行,也就是一个任务执行完成再执行下一个任务

创建队列:

- (void)createQueueTest{
    //创建队列
    dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue3 = dispatch_queue_create("串行队列", NULL);
    //获取已有队列
    //获取主队列
    dispatch_queue_t queue4 = dispatch_get_main_queue();
    //获取全局并发队列
    /*
     第一个参数是优先级,第二个无意义,填0
     */
    dispatch_queue_t queue5 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
}

3.1.2 任务

任务就是线程要执行的那段代码
执行任务有两种方式,同步和异步,同步和可以异步决定是否要开启新的线程,同步不开启,异步开启。

同步函数(sync):

异步函数(async):

提交任务的函数很多都有两个版本,一个是传两个参数,传递作为任务的block代码,一个是传三个参数,传递作为任务的方法。

- (void)createTaskTest{
    /*
     参数一:队列
     参数二:作为任务的block代码
     */
     //在调度队列上提交异步执行的块并立即返回
    dispatch_async(dispatch_queue_t  _Nonnull queue, <#^(void)block#>);
    /*
     参数一:队列
     参数二:上下文
     参数三:作为任务的函数
     */
    dispatch_async_f(<#dispatch_queue_t  _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t  _Nonnull work#>);
    
    /*
     参数一:队列
     参数二:作为任务的block代码
     */
     //提交块对象以执行,并在该块完成执行后返回。 
    dispatch_sync(dispatch_queue_t  _Nonnull queue, <#^(void)block#>);
    /*
     参数一:队列
     参数二:上下文
     参数三:作为任务的函数
     */
    dispatch_sync_f(<#dispatch_queue_t  _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t  _Nonnull work#>);
}

注意:

3.1.3 主队列

主队列是一种特殊的串行队列,特殊在于只使用在主线程和主Runloop中,并且是在启动APP时自动创建的

3.1.4 全局并发队列

全局并发队列是系统提供的,开发者可以直接使用的一个并发队列,没有其他的特殊之处。

在获取时可以给定优先级

3.1.5 总结

任务队列线程的关系.png
  • 线程有两种方式执行任务,同步和异步,分别通过同步函数和异步函数来实现
  • 同步函数添加的任务,该线程执行完这个任务才可以执行其他任务,异步函数添加的任务,线程可以并发执行该任务
  • 任务存放在队列中,队列有两种调度方式,串行和并发,串行只能顺序处理任务,并发可以同时处理多个任务
  • 线程想要并发执行任务,需要异步函数添加任务给并发队列
  • 如果队列是串行的,即使线程可以并发执行任务也不行,因为队列是串行调度给线程的。
  • 同步函数需要等待block执行完成才可以返回
  • 异步函数不需要等待block执行完成就可以返回

3.2 队列和任务的搭配使用

任务和队列以及线程的执行关系.png

说明:

  • 同步不开启新线程,异步会开启新线程
  • 并发队列可以并发调度任务,串行队列只能顺序调度任务
  • 只有并发队列提交给线程异步执行的任务才可以异步执行

总结:

执行类型.png

3.3 常用API

3.3.1 dispatch_after延迟执行

使用很简单,只是需要知道一点,等待指定的时间后将任务块异步的添加到指定的队列中,并不是延迟执行,而是延迟入队

代码:

- (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后输出");
    });
   
}

3.3.2 dispatch_once单例

dispatch_once的任务只执行一次

代码:

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

注意:

3.3.3 dispatch_apply重复执行

可以让任务重复添加到队列中重复执行

代码:

- (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后");
}

注意:

3.3.4 dispatch_group_t调度组

通过调度组将任务分组实现,调度组的最直接作用是控制任务执行顺序

API:

dispatch_group_create 创建组 
dispatch_group_async 进组任务 
dispatch_group_notify 进组任务执行完毕通知 
dispatch_group_wait 进组任务执行等待时间

//进组和出组一般是成对使用的
dispatch_group_enter 进组 
dispatch_group_leave 出组

【方式一】使用dispatch_group_async + dispatch_group_notify

设定dispatch_group_async的任务执行完才可以执行dispatch_group_notify中的任务

代码:

- (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_group_enter + dispatch_group_leave + dispatch_group_notify

代码:

- (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(@"刷新界面");
    });
}

执行结果:

2021-11-03 20:47:03.680798+0800 多线程使用[73188:1335127] 请求一完成
2021-11-03 20:47:03.680800+0800 多线程使用[73188:1335125] 请求二完成
2021-11-03 20:47:03.694075+0800 多线程使用[73188:1334877] 刷新界面

在方式二的基础上增加超时dispatch_group_wait
代码:

- (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(@"刷新界面");
    });
}

总结:

3.3.5 栅栏函数

栅栏函数有同步栅栏函数和异步栅栏函数,分别通过dispatch_barrier_sync & dispatch_barrier_async实现

栅栏函数的作用就是在自定义的并发队列中让某一部分任务按顺序执行,具有同步的效果。

同步栅栏函数dispatch_barrier_sync

//同步栅栏函数dispatch_barrier_sync
- (void)cjl_testBarrier1{
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"开始 - %@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
    });
    NSLog(@"第一次结束 - %@", [NSThread currentThread]);
    
    //栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1、任务2
    dispatch_barrier_sync(queue, ^{
        sleep(1);
        NSLog(@"------------延迟1s的栅栏任务------------%@", [NSThread currentThread]);
    });
    NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        NSLog(@"不延迟的任务2 - %@", [NSThread currentThread]);
    });
    NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}

运行结果:

2021-11-03 21:40:32.581611+0800 多线程使用[75238:1382574] 开始 - <_NSMainThread: 0x600001a7c140>{number = 1, name = main}
2021-11-03 21:40:32.581733+0800 多线程使用[75238:1382574] 第一次结束 - <_NSMainThread: 0x600001a7c140>{number = 1, name = main}
2021-11-03 21:40:34.582062+0800 多线程使用[75238:1382843] 延迟2s的任务1 - <NSThread: 0x600001a37dc0>{number = 6, name = (null)}
2021-11-03 21:40:35.582597+0800 多线程使用[75238:1382574] ------------延迟1s的栅栏任务------------<_NSMainThread: 0x600001a7c140>{number = 1, name = main}
2021-11-03 21:40:35.582839+0800 多线程使用[75238:1382574] 栅栏结束 - <_NSMainThread: 0x600001a7c140>{number = 1, name = main}
2021-11-03 21:40:35.583055+0800 多线程使用[75238:1382574] 第二次结束 - <_NSMainThread: 0x600001a7c140>{number = 1, name = main}
2021-11-03 21:40:35.583136+0800 多线程使用[75238:1382843] 不延迟的任务2 - <NSThread: 0x600001a37dc0>{number = 6, name = (null)}

说明:

异步栅栏函数dispatch_barrier_async:
代码:

//异步栅栏函数dispatch_barrier_async
- (void)cjl_testBarrier2{
    //并发队列使用栅栏函数
    
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"开始 - %@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
    });
    NSLog(@"第一次结束 - %@", [NSThread currentThread]);
    
    //由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序
    dispatch_barrier_async(queue, ^{
        sleep(1);
        NSLog(@"------------延迟1s的栅栏任务------------%@", [NSThread currentThread]);
    });
    NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        NSLog(@"不延迟的任务2 - %@", [NSThread currentThread]);
    });
    NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}

运行结果:

2021-11-03 21:44:03.482963+0800 多线程使用[75383:1386090] 开始 - <_NSMainThread: 0x600001874140>{number = 1, name = main}
2021-11-03 21:44:03.483082+0800 多线程使用[75383:1386090] 第一次结束 - <_NSMainThread: 0x600001874140>{number = 1, name = main}
2021-11-03 21:44:03.483157+0800 多线程使用[75383:1386090] 栅栏结束 - <_NSMainThread: 0x600001874140>{number = 1, name = main}
2021-11-03 21:44:03.483248+0800 多线程使用[75383:1386090] 第二次结束 - <_NSMainThread: 0x600001874140>{number = 1, name = main}
2021-11-03 21:44:05.485233+0800 多线程使用[75383:1386439] 延迟2s的任务1 - <NSThread: 0x60000182ca80>{number = 7, name = (null)}
2021-11-03 21:44:06.487452+0800 多线程使用[75383:1386439] ------------延迟1s的栅栏任务------------<NSThread: 0x60000182ca80>{number = 7, name = (null)}
2021-11-03 21:44:06.487667+0800 多线程使用[75383:1386439] 不延迟的任务2 - <NSThread: 0x60000182ca80>{number = 7, name = (null)}

说明:

总结:

  • 栅栏函数的作用是在自定义的并发队列中让某些任务按顺序执行,具有串行的效果
  • 栅栏函数只能作用于自定义的并发队列,如果作用于串行队列以及全局并发队列,它就相当于一个普通的同步函数或异步函数,无法起到栅栏的作用
  • 同步栅栏函数和异步栅栏函数的作用都是一样的,区别在于同步栅栏函数是当前线程执行的,而且必须等待栅栏任务完成才可以返回,有阻塞当前线程的效果。异步函数是新开辟线程执行的,而且无需等待任务块的完成就可以返回,没有阻塞当前线程的效果
  • 当我们想要在并发队列中控制某些任务的执行顺序时就可以使用栅栏函数

3.3.6 dispatch_semaphore_t信号量

信号量主要用作同步锁,实现线程安全,还可以用于控制GCD最大并发数。

代码:

- (void)cjl_testSemaphore{
    /*
     应用场景:同步当锁, 控制GCD最大并发数

     - dispatch_semaphore_create():创建信号量
     - dispatch_semaphore_wait():等待(减少)信号量,信号量减1。当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到有信号(信号量>=0)才可以执行下去
     - dispatch_semaphore_signal():递增信号量,信号量加1。当信号量>= 0 会执行dispatch_semaphore_wait中等待的任务

     */
    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
        });
    }
    
    //利用信号量来改写
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
            
            dispatch_semaphore_signal(sem);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
}

说明:

同步锁的使用:

最大并发数的使用:

3.3.7 dispatch_source_t计时操作

dispatch_source_t主要用于计时操作,其原因是因为它创建的timer不依赖于RunLoop,且计时精准度比NSTimer高

代码:

- (void)cjl_testSource{
    /*
     dispatch_source
     
     应用场景:GCDTimer
     在iOS开发中一般使用NSTimer来处理定时逻辑,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下。如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就挂机了;又如果Runloop在阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期。因此NSTimer在计时上会有误差,并不是特别精确,而GCD定时器不依赖Runloop,计时精度要高很多
     
     dispatch_source是一种基本的数据类型,可以用来监听一些底层的系统事件
        - Timer Dispatch Source:定时器事件源,用来生成周期性的通知或回调
        - Signal Dispatch Source:监听信号事件源,当有UNIX信号发生时会通知
        - Descriptor Dispatch Source:监听文件或socket事件源,当文件或socket数据发生变化时会通知
        - Process Dispatch Source:监听进程事件源,与进程相关的事件通知
        - Mach port Dispatch Source:监听Mach端口事件源
        - Custom Dispatch Source:监听自定义事件源

     主要使用的API:
        - dispatch_source_create: 创建事件源
        - dispatch_source_set_event_handler: 设置数据源回调
        - dispatch_source_merge_data: 设置事件源数据
        - dispatch_source_get_data: 获取事件源数据
        - dispatch_resume: 继续
        - dispatch_suspend: 挂起
        - dispatch_cancle: 取消
     */
    
    //1.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //2.创建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //3.设置timer首次执行时间,间隔,精确度
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0*NSEC_PER_SEC, 0.1*NSEC_PER_SEC);
    //4.设置timer事件回调
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCDTimer");
    });
    //5.默认是挂起状态,需要手动激活
    dispatch_resume(timer);
    
}

4、NSOperation

NSOperation和NSOperationQueue一起可以实现多线程,基于GCD的面向对象的封装,封装了很多实用的功能。

4.1 具体使用步骤

//基本使用
- (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]);
}

详细步骤:
1、先将执行的操作放到一个NSOperation对象中
2、将NSOperation对象添加到NSOperationQueue中
3、系统会自动将NSOperationQueue中的NSOperation对象取出来
4、将取出的NSOperation封装的操作放到一条新线程中执行

4.2 NSOperation的子类

NSOperation是一个抽象类,并不具备封装的能力,所以必须要使用子类,系统提供两种NSInvocationOperation、NSBlockOperation,我们还可以自定义类

4.2.1 NSInvocationOperation


//直接处理事务,不添加隐性队列
- (void)cjl_createNSOperation{
    //创建NSInvocationOperation对象并关联方法,之后start。
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"CJL"];
    
    [invocationOperation start];
}

4.2.2 NSBlockOperation

- (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];
}

4.2.3 自定义子类

继承自NSOperation,实现内部的方法

  1. 重写-(void)main方法,里面写任务
  2. 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
//*********自定义继承自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];
}

注意:自己创建自动释放池

4.3 NSOperationQueue的认识

NSOperationQueue添加事务
将NSOperation添加到NSOperationQueue,就会异步执行NSOperation的操作,操作队列不同于GCD的先进先出原则,它是添加到队列后先进入到就绪状态,之后的运行顺序由优先级决定。

有两种类型:
主队列:运行在主线程之上,可以直接获取
自定义队列:在后台执行,我们自己创建的都是自定义的

基本使用:

- (void)OperationQueue1
{
//创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];

//创建一个任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];

NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];

NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block----%@",[NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"block----%@",[NSThread currentThread]);
}];

//把任务添加到队列当中去,相当于调用了任务的start方法
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}

添加API

- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

4.4 常用的功能

4.4.1 最大并发数

最大并发数表示最多同时执行的任务数,对于多个任务可以用来控制并发、串行。

API:
属性maxConcurrentOperationCount

注意:

代码案例:

//设置并发数
- (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]);
        }];
    }
}

4.4.1 可以设置优先级

设置优先级只是抢先执行的可能性变大,并不是一定是会先执行

API:
属性qualityOfService

优先级:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};

代码案例:

- (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];

}

4.4.1 任务状态控制

可以对任务进行控制,比如取消、暂停和恢复

API
取消:

//取消多个任务
- (void)cancelAllOperations;
//取消单个任务
- (void)cancel

暂停和恢复:

- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- (BOOL)isSuspended;当前状态

注意:

  • 通过队列来暂停队列里的任务,注意暂停和恢复的是队列,而不是任务
  • 当前正在执行一个耗时操作,我们取消或者暂停时,会把当前任务执行完后再取消或暂停队列。而不是直接暂停或取消该任务

代码案例:

- (void)OperationQueue1
{
//创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];

//创建一个任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];

NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];

NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block----%@",[NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"block----%@",[NSThread currentThread]);
}];

//把任务添加到队列当中去,相当于调用了任务的start方法
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];

//取消任务一
[op1 cancel];

//暂停队列
[queue setSuspended:YES];

sleep(10);

//恢复队列
[queue setSuspended:YES];

}

4.4.1 任务依赖

NSOperation可以通过设置依赖来保证执行顺序

API:

- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//移除依赖
//添加依赖
- (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(@"执行完了?我要干其他事");
}

注意:

4.4.1 任务监听

监听一个操作是否执行完毕

API:

bo2.completionBlock = ^{
        <#code#>
    }

代码案例:

- (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");
}

注意:

线程池的了解

线程池原理.png
  1. 判断核心线程池是否都正在执行任务
    1. 返回NO,创建新的工作线程去执行
    2. 返回YES,进入【第二步】
  2. 判断线程池工作队列是否已经饱满
    1. 返回NO,将任务存储到工作队列,等待CPU调度
    2. 返回YES,进入【第三步】
  3. 判断线程池中的线程是否都处于执行状态
    1. 返回NO,安排可调度线程池中空闲的线程去执行任务
    2. 返回YES,进入【第四步】
  4. 最后交给饱和策略去执行,主要有以下四种(在iOS中并没有找到以下4种策略)
上一篇下一篇

猜你喜欢

热点阅读