不懂网站建设.怎么销售,号码网站建设,wordpress付费文章插件,网站备案必须在公司注册地http://www.jianshu.com/p/0b0d9b1f1f19 首页专题下载手机应用显示模式登录注册登录添加关注作者 伯恩的遗产 2015.07.29 00:37* 写了35249字#xff0c;被2296人关注#xff0c;获得了1668个喜欢关于iOS多线程#xff0c;你看我就够了 字数8596 阅读92152 评论153 喜欢905在…http://www.jianshu.com/p/0b0d9b1f1f19 首页专题下载手机应用 显示模式登录 注册 登录 添加关注 作者 伯恩的遗产 2015.07.29 00:37* 写了35249字被2296人关注获得了1668个喜欢 关于iOS多线程你看我就够了 字数8596 阅读92152 评论153 喜欢905 在这篇文章中我将为你整理一下 iOS 开发中几种多线程方案以及其使用方法和注意事项。当然也会给出几种多线程的案例在实际使用中感受它们的区别。还有一点需要说明的是这篇文章将会使用 Swift 和 Objective-c 两种语言讲解双语幼儿园。OKlets begin! 概述 这篇文章中我不会说多线程是什么、线程和进程的区别、多线程有什么用当然我也不会说什么是串行、什么是并行等问题这些我们应该都知道的。 在 iOS 中其实目前有 4 套多线程方案他们分别是 PthreadsNSThreadGCDNSOperation NSOperationQueue 所以接下来我会一一讲解这些方案的使用方法和一些案例。在将这些内容的时候我也会顺带说一些多线程周边产品。比如 线程同步、 延时执行、 单例模式 等等。 Pthreads 其实这个方案不用说的只是拿来充个数为了让大家了解一下就好了。百度百科里是这么说的 POSIX线程POSIX threads简称Pthreads是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统Unix、Linux、Mac OS X等中都使用Pthreads作为操作系统的线程。 简单地说这是一套在很多操作系统上都通用的多线程API所以移植性很强然并卵当然在 iOS 中也是可以的。不过这是基于 c语言 的框架使用起来这酸爽感受一下 OBJECTIVE-C 当然第一步要包含头文件 #import pthread.h 然后创建线程并执行任务 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {pthread_t thread;//创建一个线程并自动执行pthread_create(thread, NULL, start, NULL); } void *start(void *data) { NSLog(%, [NSThread currentThread]); return NULL; } 打印输出 2015-07-27 23:57:21.689 testThread[10616:2644653] NSThread: 0x7fbb48d33690{number 2, name (null)} 看代码就会发现他需要 c语言函数这是比较蛋疼的更蛋疼的是你需要手动处理线程的各个状态的转换即管理生命周期比如这段代码虽然创建了一个线程但并没有销毁。 SWIFT 很遗憾在我目前的 swift1.2 中无法执行这套方法原因是这个函数需要传入一个函数指针 CFunctionPointerT 类型但是目前 swift 无法将方法转换成此类型。听说 swift 2.0 引入一个新特性 convention(c), 可以完成 Swift 方法转换成 c 语言指针的。在这里可以看到 那么Pthreads 方案的多线程我就介绍这么多毕竟做 iOS 开发几乎不可能用到。但是如果你感兴趣的话或者说想要自己实现一套多线程方案从底层开始定制那么可以去搜一下相关资料。 NSThread 这套方案是经过苹果封装后的并且完全面向对象的。所以你可以直接操控线程对象非常直观和方便。但是它的生命周期还是需要我们手动管理所以这套方案也是偶尔用用比如 [NSThread currentThread]它可以获取当前线程类你就可以知道当前线程的各种属性用于调试十分方便。下面来看看它的一些用法。 创建并启动 先创建线程类再启动 OBJECTIVE-C // 创建NSThread *thread [[NSThread alloc] initWithTarget:self selector:selector(run:) object:nil]; // 启动 [thread start]; SWIFT //创建let thread NSThread(target: self, selector: run:, object: nil) //启动 thread.start() 创建并自动启动 OBJECTIVE-C [NSThread detachNewThreadSelector:selector(run:) toTarget:self withObject:nil]; SWIFT NSThread.detachNewThreadSelector(run:, toTarget: self, withObject: nil) 使用 NSObject 的方法创建并自动启动 OBJECTIVE-C [self performSelectorInBackground:selector(run:) withObject:nil]; SWIFT 很遗憾 too! 苹果认为 performSelector: 不安全所以在 Swift 去掉了这个方法。 Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe. 其他方法 除了创建启动外NSThread 还以很多方法下面我列举一些常见的方法当然我列举的并不完整更多方法大家可以去类的定义里去看。 OBJECTIVE-C //取消线程
- (void)cancel;//启动线程
- (void)start;//判断某个线程的状态的属性 property (readonly, getterisExecuting) BOOL executing; property (readonly, getterisFinished) BOOL finished; property (readonly, getterisCancelled) BOOL cancelled; //设置和获取线程名字 -(void)setName:(NSString *)n; -(NSString *)name; //获取当前线程信息 (NSThread *)currentThread; //获取主线程信息 (NSThread *)mainThread; //使当前线程暂停一段时间或者暂停到某个时刻 (void)sleepForTimeInterval:(NSTimeInterval)time; (void)sleepUntilDate:(NSDate *)date; SWIFT Swift的方法名字和OC的方法名都一样我就不浪费空间列举出来了。 其实NSThread 用起来也挺简单的因为它就那几种方法。同时我们也只有在一些非常简单的场景才会用 NSThread, 毕竟它还不够智能不能优雅地处理多线程中的其他高级概念。所以接下来要说的内容才是重点。 GCD Grand Central Dispatch听名字就霸气。它是苹果为多核的并行运算提出的解决方案所以会自动合理地利用更多的CPU内核比如双核、四核最重要的是它会自动管理线程的生命周期创建线程、调度任务、销毁线程完全不需要我们管理我们只需要告诉干什么就行。同时它使用的也是 c语言不过由于使用了 BlockSwift里叫做闭包使得使用起来更加方便而且灵活。所以基本上大家都使用 GCD 这套方案老少咸宜实在是居家旅行、杀人灭口必备良药。不好意思有点中二咱们继续。 任务和队列 在 GCD 中加入了两个非常重要的概念 任务 和 队列。 任务即操作你想要干什么说白了就是一段代码在 GCD 中就是一个 Block所以添加任务十分方便。任务有两种执行方式 同步执行 和 异步执行他们之间的区别是 是否会创建新的线程。 同步执行只要是同步执行的任务都会在当前线程执行不会另开线程。 异步执行只要是异步执行的任务都会另开线程在别的线程执行。 更新这里说的并不准确同步sync 和 异步async 的主要区别在于会不会阻塞当前线程直到 Block 中的任务执行完毕如果是 同步sync 操作它会阻塞当前线程并等待 Block 中的任务执行完毕然后当前线程才会继续往下运行。如果是 异步async操作当前线程会直接往下执行它不会阻塞当前线程。 队列用于存放任务。一共有两种队列 串行队列 和 并行队列。 串行队列 中的任务会根据队列的定义 FIFO 的执行一个接一个的先进先出的进行执行。 更新放到串行队列的任务GCD 会 FIFO先进先出 地取出来一个执行一个然后取下一个这样一个一个的执行。 并行队列 中的任务 根据同步或异步有不同的执行方式。 更新放到并行队列的任务GCD 也会 FIFO的取出来但不同的是它取出来一个就会放到别的线程然后再取出来一个又放到另一个的线程。这样由于取的动作很快忽略不计看起来所有的任务都是一起执行的。不过需要注意GCD 会根据系统资源控制并行的数量所以如果任务很多它并不会让所有任务同时执行。 虽然很绕但请看下表 同步执行异步执行串行队列当前线程一个一个执行其他线程一个一个执行并行队列当前线程一个一个执行开很多线程一起执行创建队列 主队列这是一个特殊的 串行队列。什么是主队列大家都知道吧它用于刷新 UI任何需要刷新 UI 的工作都要在主队列执行所以一般耗时的任务都要放到别的线程执行。 //OBJECTIVE-Cdispatch_queue_t queue ispatch_get_main_queue();//SWIFT let queue ispatch_get_main_queue() 自己创建的队列凡是自己创建的队列都是 串行队列。 其中第一个参数是标识符用于 DEBUG 的时候标识唯一的队列可以为空。大家可以看xcode的文档查看参数意义。 更新自己可以创建 串行队列, 也可以创建 并行队列。看下面的代码代码已更新它有两个参数第一个上面已经说了第二个才是最重要的。第二个参数用来表示创建的队列是串行的还是并行的传入 DISPATCH_QUEUE_SERIAL或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。 //OBJECTIVE-C//串行队列dispatch_queue_t queue dispatch_queue_create(tk.bourne.testQueue, NULL); dispatch_queue_t queue dispatch_queue_create(tk.bourne.testQueue, DISPATCH_QUEUE_SERIAL); //并行队列 dispatch_queue_t queue dispatch_queue_create(tk.bourne.testQueue, DISPATCH_QUEUE_CONCURRENT); //SWIFT //串行队列 let queue dispatch_queue_create(tk.bourne.testQueue, nil); let queue dispatch_queue_create(tk.bourne.testQueue, DISPATCH_QUEUE_SERIAL) //并行队列 let queue dispatch_queue_create(tk.bourne.testQueue, DISPATCH_QUEUE_CONCURRENT) 全局并行队列这应该是唯一一个并行队列 只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列。 //OBJECTIVE-Cdispatch_queue_t queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //SWIFT let queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 创建任务 同步任务 不会另开线程 改会阻塞当前线程 (SYNC) OBJECTIVE-C dispatch_sync(#queue#, ^{//code hereNSLog(%, [NSThread currentThread]); }); SWIFT dispatch_sync(#queue#, { () - Void in//code hereprintln(NSThread.currentThread()) }) 异步任务会另开线程 改不会阻塞当前线程 (ASYNC) OBJECTIVE-C dispatch_async(#queue#, ^{//code hereNSLog(%, [NSThread currentThread]); }); SWIFT dispatch_async(#queue#, { () - Void in//code hereprintln(NSThread.currentThread()) }) 更新为了更好的理解同步和异步和各种队列的使用下面看两个示例 示例一以下代码在主线程调用结果是什么 NSLog(之前 - %, NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () - Void in NSLog(sync - %, NSThread.currentThread()) }) NSLog(之后 - %, NSThread.currentThread()) 答案只会打印第一句之前 - NSThread: 0x7fb3a9e16470{number 1, name main} 然后主线程就卡死了你可以在界面上放一个按钮你就会发现点不了了。解释同步任务会阻塞当前线程然后把 Block 中的任务放到指定的队列中执行只有等到 Block 中的任务完成后才会让当前线程继续往下运行。那么这里的步骤就是打印完第一句后dispatch_sync 立即阻塞当前的主线程然后把 Block 中的任务放到 main_queue 中可是 main_queue 中的任务会被取出来放到主线程中执行但主线程这个时候已经被阻塞了所以 Block 中的任务就不能完成它不完成dispatch_sync 就会一直阻塞主线程这就是死锁现象。导致主线程一直卡死。 示例二以下代码会产生什么结果 let queue dispatch_queue_create(myQueue, DISPATCH_QUEUE_SERIAL)NSLog(之前 - %, NSThread.currentThread()) dispatch_async(queue, { () - Void in NSLog(sync之前 - %, NSThread.currentThread()) dispatch_sync(queue, { () - Void in NSLog(sync - %, NSThread.currentThread()) }) NSLog(sync之后 - %, NSThread.currentThread()) }) NSLog(之后 - %, NSThread.currentThread()) 答案2015-07-30 02:06:51.058 test[33329:8793087] 之前 - NSThread: 0x7fe32050dbb0{number 1, name main}2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - NSThread: 0x7fe32062e9f0{number 2, name (null)}2015-07-30 02:06:51.059 test[33329:8793087] 之后 - NSThread: 0x7fe32050dbb0{number 1, name main}很明显 sync - % 和 sync之后 - % 没有被打印出来这是为什么呢我们再来分析一下 分析我们按执行顺序一步步来哦 使用 DISPATCH_QUEUE_SERIAL 这个参数创建了一个 串行队列。打印出 之前 - % 这句。dispatch_async 异步执行所以当前线程不会被阻塞于是有了两条线程一条当前线程继续往下打印出 之后 - %这句, 另一台执行 Block 中的内容打印 sync之前 - % 这句。因为这两条是并行的所以打印的先后顺序无所谓。注意高潮来了。现在的情况和上一个例子一样了。dispatch_sync同步执行于是它所在的线程会被阻塞一直等到 sync 里的任务执行完才会继续往下。于是 sync就高兴的把自己 Block 中的任务放到 queue 中可谁想 queue 是一个串行队列一次执行一个任务所以 sync 的 Block 必须等到前一个任务执行完毕可万万没想到的是 queue 正在执行的任务就是被 sync 阻塞了的那个。于是又发生了死锁。所以 sync 所在的线程被卡死了。剩下的两句代码自然不会打印。 队列组 队列组可以将很多队列添加到一个组里这样做的好处是当这个组里所有的任务都执行完了队列组会通过一个方法通知我们。下面是使用方法这是一个很实用的功能。 OBJECTIVE-C //1.创建队列组
dispatch_group_t group dispatch_group_create();
//2.创建队列
dispatch_queue_t queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//3.多次使用队列组的方法执行任务, 只有异步方法 //3.1.执行3次循环 dispatch_group_async(group, queue, ^{ for (NSInteger i 0; i 3; i) { NSLog(group-01 - %, [NSThread currentThread]); } }); //3.2.主队列执行8次循环 dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (NSInteger i 0; i 8; i) { NSLog(group-02 - %, [NSThread currentThread]); } }); //3.3.执行5次循环 dispatch_group_async(group, queue, ^{ for (NSInteger i 0; i 5; i) { NSLog(group-03 - %, [NSThread currentThread]); } }); //4.都完成后会自动通知 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(完成 - %, [NSThread currentThread]); }); SWIFT //1.创建队列组
let group dispatch_group_create()
//2.创建队列
let queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //3.多次使用队列组的方法执行任务, 只有异步方法 //3.1.执行3次循环 dispatch_group_async(group, queue) { () - Void in for _ in 0..3 { NSLog(group-01 - %, NSThread.currentThread()) } } //3.2.主队列执行8次循环 dispatch_group_async(group, dispatch_get_main_queue()) { () - Void in for _ in 0..8 { NSLog(group-02 - %, NSThread.currentThread()) } } //3.3.执行5次循环 dispatch_group_async(group, queue) { () - Void in for _ in 0..5 { NSLog(group-03 - %, NSThread.currentThread()) } } //4.都完成后会自动通知 dispatch_group_notify(group, dispatch_get_main_queue()) { () - Void in NSLog(完成 - %, NSThread.currentThread()) } 打印结果 2015-07-28 03:40:34.277 test[12540:3319271] group-03 - NSThread: 0x7f9772536f00{number 3, name (null)} 2015-07-28 03:40:34.277 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.277 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.277 test[12540:3319271] group-03 - NSThread: 0x7f9772536f00{number 3, name (null)} 2015-07-28 03:40:34.278 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.278 test[12540:3319271] group-03 - NSThread: 0x7f9772536f00{number 3, name (null)} 2015-07-28 03:40:34.278 test[12540:3319271] group-03 - NSThread: 0x7f9772536f00{number 3, name (null)} 2015-07-28 03:40:34.278 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.277 test[12540:3319273] group-01 - NSThread: 0x7f977272e8d0{number 2, name (null)} 2015-07-28 03:40:34.278 test[12540:3319271] group-03 - NSThread: 0x7f9772536f00{number 3, name (null)} 2015-07-28 03:40:34.278 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.278 test[12540:3319273] group-01 - NSThread: 0x7f977272e8d0{number 2, name (null)} 2015-07-28 03:40:34.278 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.278 test[12540:3319273] group-01 - NSThread: 0x7f977272e8d0{number 2, name (null)} 2015-07-28 03:40:34.279 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.279 test[12540:3319146] group-02 - NSThread: 0x7f977240ba60{number 1, name main} 2015-07-28 03:40:34.279 test[12540:3319146] 完成 - NSThread: 0x7f977240ba60{number 1, name main} 这些就是 GCD 的基本功能但是它的能力远不止这些等讲完 NSOperation 后我们再来看看它的一些其他方面用途。而且只要你想象力够丰富你可以组合出更好的用法。 更新关于GCD还有两个需要说的 func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):这个方法重点是你传入的 queue当你传入的 queue 是通过 DISPATCH_QUEUE_CONCURRENT 参数自己创建的 queue 时这个方法会阻塞这个 queue注意是阻塞 queue 而不是阻塞当前线程一直等到这个 queue 中排在它前面的任务都执行完成后才会开始执行自己自己执行完毕后再会取消阻塞使这个 queue 中排在它后面的任务继续执行。如果你传入的是其他的 queue, 那么它就和 dispatch_async 一样了。 func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):这个方法的使用和上一个一样传入 自定义的并发队列DISPATCH_QUEUE_CONCURRENT它和上一个方法一样的阻塞 queue不同的是 这个方法还会 阻塞当前线程。如果你传入的是其他的 queue, 那么它就和 dispatch_sync 一样了。 NSOperation和NSOperationQueue NSOperation 是苹果公司对 GCD 的封装完全面向对象所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解 将要执行的任务封装到一个 NSOperation 对象中。将此任务添加到一个 NSOperationQueue 对象中。然后系统就会自动在执行任务。至于同步还是异步、串行还是并行请继续往下看 添加任务 值得说明的是NSOperation 只是一个抽象类所以不能封装任务。但它有 2 个子类用于封装任务。分别是NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后需要调用 start 方法来启动任务它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务只需要调用其 cancel 方法即可。 NSInvocationOperation : 需要传入一个方法名。 OBJECTIVE-C //1.创建NSInvocationOperation对象NSInvocationOperation *operation [[NSInvocationOperation alloc] initWithTarget:self selector:selector(run) object:nil]; //2.开始执行 [operation start]; SWIFT 在 Swift 构建的和谐社会里是容不下 NSInvocationOperation 这种不是类型安全的败类的。苹果如是说。这里有相关解释 NSBlockOperation OBJECTIVE-C //1.创建NSBlockOperation对象NSBlockOperation *operation [NSBlockOperation blockOperationWithBlock:^{NSLog(%, [NSThread currentThread]); }]; //2.开始任务 [operation start]; SWIFT //1.创建NSBlockOperation对象let operation NSBlockOperation { () - Void in println(NSThread.currentThread()) } //2.开始任务 operation.start() 之前说过这样的任务默认会在当前线程执行。但是 NSBlockOperation 还有一个方法addExecutionBlock: 通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行它会 在主线程和其它的多个线程 执行这些任务注意下面的打印结果 OBJECTIVE-C //1.创建NSBlockOperation对象NSBlockOperation *operation [NSBlockOperation blockOperationWithBlock:^{NSLog(%, [NSThread currentThread]); }]; //添加多个Block for (NSInteger i 0; i 5; i) { [operation addExecutionBlock:^{ NSLog(第%ld次%, i, [NSThread currentThread]); }]; } //2.开始任务 [operation start]; SWIFT //1.创建NSBlockOperation对象let operation NSBlockOperation { () - Void in NSLog(%, NSThread.currentThread()) } //2.添加多个Block for i in 0..5 { operation.addExecutionBlock { () - Void in NSLog(第%ld次 - %, i, NSThread.currentThread()) } } //2.开始任务 operation.start() 打印输出 2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - NSThread: 0x7ff5c9701910{number 1, name main} 2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - NSThread: 0x7ff5c972caf0{number 4, name (null)} 2015-07-28 17:50:16.585 test[17527:4095665] NSThread: 0x7ff5c961b610{number 3, name (null)} 2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - NSThread: 0x7ff5c948d310{number 2, name (null)} 2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - NSThread: 0x7ff5c972caf0{number 4, name (null)} 2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - NSThread: 0x7ff5c9701910{number 1, name main} NOTEaddExecutionBlock 方法必须在 start() 方法之前执行否则就会报错 ‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished NOTE大家可能发现了一个问题为什么我在 Swift 里打印输出使用 NSLog() 而不是 println() 呢原因是使用 print() / println() 输出的话它会简单地使用 流stream 的概念学过 C 的都知道。它会把需要输出的每个字符一个一个的输出到控制台。普通使用并没有问题可是当多线程同步输出的时候问题就来了由于很多 println() 同时打印就会导致控制台上的字符混乱的堆在一起而NSLog() 就没有这个问题。到底是什么样子的呢你可以把上面 NSLog() 改为 println() 然后一试便知。 更多 NSLog() 与 println() 的区别看这里 自定义Operation 除了上面的两种 Operation 以外我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类并实现其 main() 方法因为在调用 start()方法的时候内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外你还需要实现 cancel() 在内的各种方法。所以这个功能提供给高级玩家我在这里就不说了等我需要用到时在研究它到时候可能会再做更新。 创建队列 看过上面的内容就知道我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法也会在 当前线程和其他线程 中执行也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且按类型来说的话一共有两种类型主队列、其他队列。只要添加到队列会自动调用任务的 start() 方法 主队列 细心的同学就会发现每套多线程方案都会有一个主线程当然啦说的是iOS中像 pthread 这种多系统的方案并没有因为 UI线程 理论需要每种操作系统自己定制。这是一个特殊的线程必须串行。所以添加到主队列的任务都会一个接一个地排着队在主线程处理。 //OBJECTIVE-C
NSOperationQueue *queue [NSOperationQueue mainQueue];//SWIFT
let queue NSOperationQueue.mainQueue() 其他队列 因为主队列比较特殊所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列了因为只有这两种队列除了主队列其他队列就不需要名字了。 注意其他队列的任务会在其他线程并行执行。 OBJECTIVE-C //1.创建一个其他队列
NSOperationQueue *queue [[NSOperationQueue alloc] init];//2.创建NSBlockOperation对象
NSBlockOperation *operation [NSBlockOperation blockOperationWithBlock:^{ NSLog(%, [NSThread currentThread]); }]; //3.添加多个Block for (NSInteger i 0; i 5; i) { [operation addExecutionBlock:^{ NSLog(第%ld次%, i, [NSThread currentThread]); }]; } //4.队列添加任务 [queue addOperation:operation]; SWIFT //1.创建其他队列
let queue NSOperationQueue()//2.创建NSBlockOperation对象
let operation NSBlockOperation { () - Void in NSLog(%, NSThread.currentThread()) } //3.添加多个Block for i in 0..5 { operation.addExecutionBlock { () - Void in NSLog(第%ld次 - %, i, NSThread.currentThread()) } } //4.队列添加任务 queue.addOperation(operation) 打印输出 2015-07-28 20:26:28.463 test[18622:4443534] NSThread: 0x7fd022c3ac10{number 5, name (null)} 2015-07-28 20:26:28.463 test[18622:4443536] 第2次 - NSThread: 0x7fd022e36d50{number 2, name (null)} 2015-07-28 20:26:28.463 test[18622:4443535] 第0次 - NSThread: 0x7fd022f237f0{number 4, name (null)} 2015-07-28 20:26:28.463 test[18622:4443533] 第1次 - NSThread: 0x7fd022d372b0{number 3, name (null)} 2015-07-28 20:26:28.463 test[18622:4443534] 第3次 - NSThread: 0x7fd022c3ac10{number 5, name (null)} 2015-07-28 20:26:28.463 test[18622:4443536] 第4次 - NSThread: 0x7fd022e36d50{number 2, name (null)} OK, 这时应该发问了大家将 NSOperationQueue 与 GCD的队列 相比较就会发现这里没有串行队列那如果我想要10个任务在其他线程串行的执行怎么办 这就是苹果封装的妙处你不用管串行、并行、同步、异步这些名词。NSOperationQueue有一个参数 maxConcurrentOperationCount 最大并发数用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候他不就是串行了嘛 NSOperationQueue 还有一个添加任务的方法- (void)addOperationWithBlock:(void (^)(void))block; 这是不是和 GCD 差不多这样就可以添加一个任务到队列中了十分方便。 NSOperation 有一个非常实用的功能那就是添加依赖。比如有 3 个任务A: 从服务器上下载一张图片B给这张图片加个水印C把图片返回给服务器。这时就可以用到依赖了: OBJECTIVE-C //1.任务一下载图片
NSBlockOperation *operation1 [NSBlockOperation blockOperationWithBlock:^{NSLog(下载图片 - %, [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; //2.任务二打水印 NSBlockOperation *operation2 [NSBlockOperation blockOperationWithBlock:^{ NSLog(打水印 - %, [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; //3.任务三上传图片 NSBlockOperation *operation3 [NSBlockOperation blockOperationWithBlock:^{ NSLog(上传图片 - %, [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; //4.设置依赖 [operation2 addDependency:operation1]; //任务二依赖任务一 [operation3 addDependency:operation2]; //任务三依赖任务二 //5.创建队列并加入任务 NSOperationQueue *queue [[NSOperationQueue alloc] init]; [queue addOperations:[operation3, operation2, operation1] waitUntilFinished:NO]; SWIFT //1.任务一下载图片
let operation1 NSBlockOperation { () - Void in NSLog(下载图片 - %, NSThread.currentThread()) NSThread.sleepForTimeInterval(1.0) } //2.任务二打水印 let operation2 NSBlockOperation { () - Void in NSLog(打水印 - %, NSThread.currentThread()) NSThread.sleepForTimeInterval(1.0) } //3.任务三上传图片 let operation3 NSBlockOperation { () - Void in NSLog(上传图片 - %, NSThread.currentThread()) NSThread.sleepForTimeInterval(1.0) } //4.设置依赖 operation2.addDependency(operation1) //任务二依赖任务一 operation3.addDependency(operation2) //任务三依赖任务二 //5.创建队列并加入任务 let queue NSOperationQueue() queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false) 打印结果 2015-07-28 21:24:28.622 test[19392:4637517] 下载图片 - NSThread: 0x7fc10ad4d970{number 2, name (null)} 2015-07-28 21:24:29.622 test[19392:4637515] 打水印 - NSThread: 0x7fc10af20ef0{number 3, name (null)} 2015-07-28 21:24:30.627 test[19392:4637515] 上传图片 - NSThread: 0x7fc10af20ef0{number 3, name (null)} 注意不能添加相互依赖会死锁比如 A依赖BB依赖A。可以使用 removeDependency 来解除依赖关系。可以在不同的队列之间依赖反正就是这个依赖是添加到任务身上的和队列没关系。其他方法 以上就是一些主要方法, 下面还有一些常用方法需要大家注意 NSOperation BOOL executing; //判断任务是否正在执行 BOOL finished; //判断任务是否完成 void (^completionBlock)(void); //用来设置完成后需要执行的操作 - (void)cancel; //取消任务 - (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕 NSOperationQueue NSUInteger operationCount; //获取队列的任务数 - (void)cancelAllOperations; //取消队列中所有的任务 - (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕 [queue setSuspended:YES]; // 暂停queue [queue setSuspended:NO]; // 继续queue 好啦到这里差不多就讲完了。当然我讲的并不完整可能有一些知识我并没有讲到但作为常用方法这些已经足够了。不过我在这里只是告诉你了一些方法的功能只是怎么把他们用到合适的地方就需要多多实践了。下面我会说一些关于多线程的案例是大家更加什么地了解。 其他用法 在这部分我会说一些和多线程知识相关的案例可能有些很简单大家早都知道的不过因为这篇文章讲的是多线程嘛所以应该尽可能的全面嘛。还有就是我会尽可能的使用多种方法实现让大家看看其中的区别。 线程同步 所谓线程同步就是为了防止多个线程抢夺同一个资源造成的数据安全问题所采取的一种措施。当然也有很多实现方法请往下看 互斥锁 给需要同步的代码块加一个互斥锁就可以保证每次只有一个线程访问此代码块。 OBJECTIVE-C synchronized(self) {//需要执行的代码块
} SWIFT objc_sync_enter(self)
//需要执行的代码块
objc_sync_exit(self) 同步执行 我们可以使用多线程的知识把多个线程都要执行此段代码添加到同一个串行队列这样就实现了线程同步的概念。当然这里可以使用 GCD 和 NSOperation 两种方案我都写出来。 OBJECTIVE-C //GCD//需要一个全局变量queue要让所有线程的这个操作都加到一个queue中dispatch_sync(queue, ^{NSInteger ticket lastTicket;[NSThread sleepForTimeInterval:0.1]; NSLog(%ld - %,ticket, [NSThread currentThread]); ticket - 1; lastTicket ticket; }); //NSOperation NSOperationQueue //重点1. 全局的 NSOperationQueue, 所有的操作添加到同一个queue中 // 2. 设置 queue 的 maxConcurrentOperationCount 为 1 // 3. 如果后续操作需要Block中的结果就需要调用每个操作的waitUntilFinished阻塞当前线程一直等到当前操作完成才允许执行后面的。waitUntilFinished 要在添加到队列之后 NSBlockOperation *operation [NSBlockOperation blockOperationWithBlock:^{ NSInteger ticket lastTicket; [NSThread sleepForTimeInterval:1]; NSLog(%ld - %,ticket, [NSThread currentThread]); ticket - 1; lastTicket ticket; }]; [queue addOperation:operation]; [operation waitUntilFinished]; //后续要做的事 SWIFT 这里的 swift 代码我就不写了因为每句都一样只是语法不同而已照着 OC 的代码就能写出 Swift 的。这篇文章已经老长老长了我就不浪费篇幅了又不是高中写作文。 延迟执行 所谓延迟执行就是延时一段时间再执行某段代码。下面说一些常用方法。 perform OBJECTIVE-C // 3秒后自动调用self的run:方法并且传递参数abc[self performSelector:selector(run:) withObject:abc afterDelay:3]; SWIFT 之前就已经说过Swift 里去掉了这个方法。 GCD 可以使用 GCD 中的 dispatch_after 方法OC 和 Swift 都可以使用这里只写 OC 的Swift 的是一样的。 OBJECTIVE-C // 创建队列
dispatch_queue_t queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 设置延时单位秒 double delay 3; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{ // 3秒后需要执行的任务 }); NSTimer NSTimer 是iOS中的一个计时器类除了延迟执行还有很多用法不过这里直说延迟执行的用法。同样只写 OC 版的Swift 也是相同的。 OBJECTIVE-C [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:selector(run:) userInfo:abc repeats:NO]; 单例模式 至于什么是单例模式我也不多说我只说说一般怎么实现。在 Objective-C 中实现单例的方法已经很具体了虽然有别的方法但是一般都是用一个标准的方法了下面来看看。 OBJECTIVE-C interface Tool : NSObject NSCopying (instancetype)sharedTool; end implementation Tool static id _instance; (instancetype)sharedTool { static dispatch_once_t onceToken; dispatch_once(onceToken, ^{ _instance [[Tool alloc] init]; }); return _instance; } end 这里之所以将单例模式是因为其中用到了 GCD 的 dispatch_once 方法。下面看 Swift 中的单例模式在Swift中单例模式非常简单想知道怎么从 OC 那么复杂的方法变成下面的写法的请看这里 SWIFT class Tool: NSObject {static let sharedTool Tool() // 私有化构造方法阻止其他对象使用这个类的默认的()构造方法 private override init() {} } 从其他线程回到主线程的方法 我们都知道在其他线程操作完成后必须到主线程更新UI。所以介绍完所有的多线程方案后我们来看看有哪些方法可以回到主线程。 NSThread //Objective-C
[self performSelectorOnMainThread:selector(run) withObject:nil waitUntilDone:NO]; //Swift //swift 取消了 performSelector 方法。 GCD //Objective-C
dispatch_async(dispatch_get_main_queue(), ^{});//Swift
dispatch_async(dispatch_get_main_queue(), { () - Void in }) NSOperationQueue //Objective-C
[[NSOperationQueue mainQueue] addOperationWithBlock:^{}];//Swift
NSOperationQueue.mainQueue().addOperationWithBlock { () - Void in } 总结 好的吧总算写完了纯手敲6k多字感动死我了。花了两天时间跨度有点大所以可能有些地方上段不接下段或者有的地方不完整如果你看着比较费力或者有什么地方有问题都可以在评论区告诉我我会及时修改的。当然啦多线程的东西也不止这些题目也就只是个题目不要当真。想要了解更多的东西还得自己去网上挖掘相关资料。多看看官方文档。实在是编不下去了大家好好看~。对了看我写的这么卖力不打赏的话得点个喜欢也是极好的。 更新第一次放出来的时候有很多地方有错误很感谢有朋友提出来了。如果你看到有错误的地方一定记得指出来这样对大家都有帮助。还有一点对初学者来说遇到不懂的方法最好的办法就是查看官方文档那里是最准确的就算有几个单词不认识查一下就好了不会影响对整体的理解。我看到有网站转载了我的文章但转载的可能存在问题而我只能在简书上更新所以如果要看 完整版本 还是到简书来看吧这里是地址。 推荐拓展阅读 著作权归作者所有 您觉得这篇文章对你有帮助吗看我这么用心鼓励一下呗~ ¥ 打赏支持 喜欢 905 分享到微博 分享到微信 更多分享 × 喜欢的用户 疯狂的风2016.08.25 12:59 zone_2016.08.25 12:04 9cdab9a001ba2016.08.25 09:34 cablink2016.08.24 22:51 KvAllen2016.08.24 10:15 SMFly2016.08.24 08:35 ASDFMDB2016.08.24 01:39 BirdCH2016.08.23 18:56 Arvin_sir2016.08.23 16:08 xzm_prefect2016.08.23 14:55 妈妈说不要跟昵称太长的人一起玩2016.08.23 11:11 冷雨夜不回家2016.08.23 08:42 海宠2016.08.23 00:45 sunnyXG2016.08.22 23:28 寒冬62016.08.22 21:25 153条评论 按时间正序· 按时间倒序· 按喜欢排序 添加新评论 从今以后 2 楼 · 2015.07.29 02:37 这是我的一些看法:同步派发(sync)会尽可能地在当前线程派发任务.但如果在其他队列往主队列同步派发,任务会在主线程执行.异步派发(async)也不绝对会另开线程.例如在主线程异步派发到主线程,派发依旧是异步的,任务也会在主线程执行.我感觉同步异步的重要区别在于派发方法是否需要等待 block 完成后才能返回.无论串行还是并发队列,任务启动顺序都是按照 FIFO 的,只是并发队列允许同一时间有多个任务执行都在执行.创建队列也可以自己创建并发队列.dispatch_queue_create(我是私有串行队列, DISPATCH_QUEUE_SERIAL);dispatch_queue_create(我是私有并发队列, DISPATCH_QUEUE_CONCURRENT);dispatch_async(dispatch_get_main_queue()) {// Swift 中用尾闭包写法感觉更美观.}关于同步,还有这么个东西:dispatch_barrier_async(privateConcurrentQueue, ^{// 写入操作会确保队列前面的操作执行完毕才开始,并会阻塞队列中后来的操作.});dispatch_sync(privateConcurrentQueue, ^{// 只要没有写入操作,多个读取操作是相对并行的.}); 喜欢(4)回复 伯恩的遗产 从今以后 哇看了你说的感觉对 gcd 理解更深刻了。好多东西写的时候都没注意刚才看了下文档才发现果然是的。非常感谢你的分享。 回复2015.07.29 03:31 南栀倾寒 可以将楼上的补充 到文章去 因为同步异步并不是觉得开线程的问题 还有barrier这个操作叫做只写锁 其他同步或者异步都是并发写操作的 如果是只读的用sync 和async 就行 如果需要并发写操作 就要注意barrier了 回复2015.07.29 17:29 伯恩的遗产 南栀倾寒 白天有点事儿我刚刚更新了。?? 回复2015.07.30 03:17 还有 9 条评论 展开查看 添加新回复 c1ear 3 楼 · 2015.07.29 09:16 gcd缺点执行任务后无法cancel~~~ 喜欢(0)回复 HaodanHuang 项羽Vip 其实可以cancel 但没有内建方法只需要自己用一个iVariable在block被执行前判断即可。 回复2015.08.13 09:28 Raybon_lee HaodanHuang 这个是什么方法可否告知一下呢 回复2015.11.25 23:58 qYong_ HaodanHuang 能具体说一下么求告知 回复2015.12.16 14:00 还有 2 条评论 展开查看 添加新回复 Kimball 7 楼 · 2015.07.29 10:55 对新手来说挺受用的 喜欢(0)回复 Kent_Zhang 8 楼 · 2015.07.29 11:13 线程互斥还可以介绍下Unix传统手段嘛文件锁信号量线程mutex 喜欢(0)回复 dreday 10 楼 · 2015.07.29 13:32 顶顶顶好文必须顶 喜欢(0)回复 dreday 11 楼 · 2015.07.29 13:32 项羽Vip 赞同 喜欢(0)回复 Mars飘殇 12 楼 · 2015.07.29 13:43 ? 喜欢(0)回复 JanzTam 14 楼 · 2015.07.29 22:09 评论的都是大神 喜欢(0)回复 庞大不小 15 楼 · 2015.07.29 23:50 收益很多。让我再重新复习了一下多线程 喜欢(0)回复 isaced 17 楼 · 2015.07.30 15:11 lett begin! 什么鬼 喜欢(0)回复 伯恩的遗产 isaced 不要在意这些细节 回复2015.07.30 15:58 添加新回复 像羽毛那样轻 20 楼 · 2015.07.30 17:05 有这么深的领悟学成必成大神 喜欢(0)回复 龙哥盟飞龙 22 楼 · 2015.07.31 11:00 纠正。线程在游离态下结束后自动销毁。pthread库中调用detach()win32api用调用CloseHandle()。详见《csapp》。 喜欢(0)回复 胖子程 龙哥盟飞龙 楼主为什么不回复你 回复2016.08.10 15:22 添加新回复 CRUFT 26 楼 · 2015.08.02 22:11 “更新放到串行队列的任务GCD 也会 FIFO的取出来但不同的是它取出来一个就会放到别的线程然后再取出来一个又放到另一个的线程。”是不是应该是“放到并行队列的任务”“可以 main_queue 中的任务会被取出来放到主线程中执行但主线程这个时候已经被阻塞了”“可以”是否应该是“可是” 喜欢(0)回复 伯恩的遗产 d546e5af7f42 嗯嗯已改正你看的很仔细呀~ 回复2015.08.02 22:38 添加新回复 685d7c56abb0 27 楼 · 2015.08.03 09:57 ... 喜欢(0)回复 马来 28 楼 · 2015.08.03 10:12 对于dispatch source 有用过吗 喜欢(0)回复 伯恩的遗产 马来 这个还没用过 回复2015.08.03 13:41 添加新回复 加载更多 登录后发表评论 被以下专题收入发现更多相似内容 程序员 添加关注 如果你是程序员或者有一颗喜欢写程序的心喜欢分享技术干货、项目经验、程序员日常囧事等等欢迎投稿《程序员》专题。 专题主编小... 21820篇文章 · 152343人关注 首页投稿 添加关注 玩转简书的第一步从这个专题开始。 想上首页热门榜么好内容想被更多人看到么来投稿吧如果被拒也不要灰心哦入选文章会进一个队... 92920篇文章 · 124794人关注 iOS开发技巧 添加关注 【简介】 专题内容主要包括OC、swift等涉及到iOS开发进阶的内容。 swift可以关注下我的另一个专题 swift开发... 1046篇文章 · 19086人关注 转载于:https://www.cnblogs.com/lihaibo-Leao/p/5806808.html