OC-底层原理-28-GCD-之-NSThread-&-GCD-&-NSOperation

203 阅读7分钟

本文的主要目的是介绍 NSThread、GCD、NSOperation常见的使用方式

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

创建线程 线程的创建方式主要以下三种方式

通过init初始化方式创建

通过detachNewThreadSelector构造器方式创建

通过performSelector...方法创建,主要是用于获取主线程,以及后台线程

//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]); } 属性

  • thread.isExecuting //线程是否在执行

  • thread.isCancelled //线程是否被取消

  • thread.isFinished //是否完成

  • thread.isMainThread //是否是主线程

  • thread.threadPriority //线程的优先级,取值范围0.0-1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高 类方法 常用的类方法有以下几个:

currentThread:获取当前线程

sleep...:阻塞线程

exit:退出线程

mainThread:获取主线程

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

} dispatch_once

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

有以下两种使用方式

【方式一】使用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(@"刷新界面"); }); } 在方式二的基础上增加超时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(@"刷新界面");
});

} dispatch_barrier_sync & dispatch_barrier_async

栅栏函数,主要有两种使用场景:串行队列、并发队列

  • (void)cjl_testBarrier{ /* dispatch_barrier_sync & dispatch_barrier_async

    应用场景:同步锁

    等栅栏前追加到队列中的任务执行完毕后,再将栅栏后的任务追加到队列中。 简而言之,就是先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务

    • dispatch_barrier_async:前面的任务执行完毕才会来到这里

    • dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行

    • dispatch_barrier_async可以控制队列中任务的执行顺序,

    • 而dispatch_barrier_sync不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用) */

    [self cjl_testBarrier1]; [self cjl_testBarrier2]; }

  • (void)cjl_testBarrier1{ //串行队列使用栅栏函数

    dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);

    NSLog(@"开始 - %@", [NSThread currentThread]); dispatch_async(queue, ^{ sleep(2); NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]); }); NSLog(@"第一次结束 - %@", [NSThread currentThread]);

    //栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1、任务2 dispatch_barrier_async(queue, ^{ NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]); }); NSLog(@"栅栏结束 - %@", [NSThread currentThread]);

    dispatch_async(queue, ^{ sleep(2); NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]); }); NSLog(@"第二次结束 - %@", [NSThread currentThread]); }

  • (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, ^{ NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]); }); NSLog(@"栅栏结束 - %@", [NSThread currentThread]);

    dispatch_async(queue, ^{ sleep(2); NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]); }); NSLog(@"第二次结束 - %@", [NSThread currentThread]); } dispatch_semaphore_t

信号量主要用作同步锁,用于控制GCD最大并发数

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

    • dispatch_semaphore_create():创建信号量
    • dispatch_semaphore_wait():等待信号量,信号量减1。当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)才执行下去
    • dispatch_semaphore_signal():释放信号量,信号量加1。当信号量>= 0 会执行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);
    

    } } 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.0NSEC_PER_SEC, 0.1NSEC_PER_SEC); //4.设置timer事件回调 dispatch_source_set_event_handler(timer, ^{ NSLog(@"GCDTimer"); }); //5.默认是挂起状态,需要手动激活 dispatch_resume(timer);

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

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

1、创建任务:先将需要执行的操作封装到NSOperation对象中。

2、创建队列:创建NSOperationQueue。

3、将任务加入到队列中:将NSOperation对象添加到NSOperationQueue中。

//基本使用

  • (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

//直接处理事务,不添加隐性队列

  • (void)cjl_createNSOperation{ //创建NSInvocationOperation对象并关联方法,之后start。 NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"CJL"];

    [invocationOperation start]; } 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]; } 3、定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。 //自定义继承自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有两种队列:主队列、其他队列。其他队列包含了 串行和并发。

主队列:主队列上的任务是在主线程执行的。

其他队列(非主队列):加入到'非队列'中的任务默认就是并发,开启多线程。

  • (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"); } 设置执行顺序

//执行顺序

  • (void)cjl_testQueueSequence{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; for (int i = 0; i < 5; i++) { [queue addOperationWithBlock:^{ NSLog(@"%@---%d", [NSThread currentThread], i); }]; } } 设置优先级

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

} 设置并发数

//设置并发数

  • (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]); }]; } } 添加依赖

//添加依赖

  • (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(@"执行完了?我要干其他事"); } 线程间通讯

//线程间通讯

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

    }];

}