iOS实现多线程的方式有三种,分别是NSThreadNSOperationGCD

关于GCD,请阅读GCD深入浅出学习

简介


NSOperation封装了需要执行的操作和执行操作所需的数据,提供了并发或非并发操作,可以设置最大并发数,取消操作等。

iOS使用NSOperation的方式有两种: * 直接使用系统提供的两个子类:NSInvocationOperationNSBlockOperation * 继承于NSOperation

这里所说的抽象类不是真正的抽象类,不像C++那种纯虚函数,不能实例化。在Ojbective-C中是没有纯虚函数的,因此它是可以实例化的。只是由于没有提供任务接口,因此实例化了也没有意义。

 
1
2
3
 
NSOperation *op = [[NSOperation alloc] init];
 

注意:我们不能直接使用NSOperation这个类,这个类相当于一个抽象类,不能直接实例化,必须重写main方法。

NSOperation基类API


下面简单说明NSOperation所提供的一些操作。

1.执行任务


NSOperation提供了start方法开启任务执行操作,NSOperation对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行。

 
1
2
3
4
5
 
if (!operation.isExecuting) {
  [operation start];
}
 

2.判断是否是同步还是异步


NSOperation提供的isConcurrent可判断是同步还是异步执行。isConcurrent默认值为NO,表示操作与调用线程同步执行。不过这个方法在7.0之后就被废弃了,改成使用isAsynchronous判断了。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
if ([UIDevice currentDevice].systemVersion.intValue >= 7.0) {
  if (operation.isAsynchronous) {
    NSLog(@"异步");
  } else {
    NSLog(@"同步");
  }
} else {
  if (operation.isConcurrent) {
    NSLog(@"异步");
  } else {
    NSLog(@"同步");
  }
}
 

3.判断任务是否在执行中


NSOperation提供了isExecuting,可判断任务是否正在执行中。

 
1
2
3
4
5
 
if (!operation.isExecuting) {
  [operation start];
}
 

4.判断任务是否已经准备好


NSOperation提供了isReady方法来获取任务是否已经为执行准备好。

 
1
2
3
4
5
 
if (!operation.isReady) {
  [operation start];
}
 

5.判断任务已经已完成


NSOperation提供了isFinished,可判断任务是否已经执行完成。

 
1
2
3
4
5
 
if (operation.isFinished) {
  NSLog(@"finished");
}
 

6.取消任务/判断任务状态

NSOperation提供了isCancelled,可判断任务是否已经执行完成,而要取消任务,可调用cancel方法。

 
1
2
3
4
5
 
if (!operation.isCancelled) {
    [operation cancel];
}
 

7.任务完成回调


如果我们想在一个NSOperation执行完毕后做一些事情,可以调用NSOperationcompletionBlock属性来设置在任务完成以后我们还想做的事情。

我们可以通过这种点语法设置:

 
1
2
3
4
5
 
operation.completionBlock = ^() {
  NSLog(@"任务执行完毕");
};
 

也可以通过中括号方式设置:

 
1
2
3
4
5
 
[operation setCompletionBlock:^{
  NSLog(@"任务执行完毕");
}];
 

8.任务优先级


如下,NSOperation为我们提供了在NSOperationQueue调度队列中任务的优先级设置。

 
1
2
3
4
5
6
7
8
9
10
11
 
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
 
@property NSOperationQueuePriority queuePriority;
 

NSInvocationOperation子类


NSInvocationOperation是继承于NSOperation,提供创建任务的方式是通过selector

 
1
2
3
4
 
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
 

对于第二个初始化方法已经被废弃了,第二个初始化方法是通过运行时的方式来添加任务的,操作起来比较复杂。第一种就是很普通的方式,是很常见的target-action设计模式。

 
1
2
3
4
5
 
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(updateUI) object:nil];
// 开始执行任务(同步执行)
[operation start];
 

调用start方法是同步执行的。如果要异步执行,可以放到NSOperationQueue队列中,它就相当于一个线程池,而且任务一旦放进去,就会按照FIFO的原则严格执行任务。任务放到线程池中后,是否会马上执行,是根据当前所设置的并发数量决定的。

看看我们下载一个图片:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
- (void)test1 {
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                         selector:@selector(downloadImage:)
                                                                           object:@"图片的URL"];
  
  NSOperationQueue *queue = [[NSOperationQueue alloc]init];
  [queue addOperation:operation];
}
 
- (void)downloadImage:(NSString *)url {
  NSURL *nsUrl = [NSURL URLWithString:url];
  NSData *data = [[NSData alloc] initWithContentsOfURL:nsUrl];
  UIImage *image = [[UIImage alloc] initWithData:data];
  
  dispatch_async(dispatch_get_main_queue(), ^{
    self.imageView.image = image;
  });
}
 

我们需要注意,最后在更新UI的时候,一定要回到主线程,否则UI效果不会马上变化。当然,我们也可以使用别的方式回到主线程更新UI

 
1
2
3
4
5
6
7
 
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];  
 
- (void)updateUI:(UIImage *) image{  
    self.imageView.image = image;  
}  
 

NSBlockOperation子类


NSBlockOperation是直接继承于NSOperation的子类,它能够并发地执行一个或多个block对象,所有的block都执行完之后,操作才算真正完成。

添加任务


NSBlockOperation都是block任务,操作起来比较简洁一些。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
  NSLog(@"这是第一个任务在线程:%@执行,isMainThread: %d,isAync: %d",
        [NSThread currentThread],
        [NSThread isMainThread],
        [operation isAsynchronous]);
}];
 
__weak typeof(operation) weakOperation = operation;
[operation addExecutionBlock:^() {
  NSLog(@"这是第二个任务在线程:%@执行,isMainThread: %d,isAync: %d",
        [NSThread currentThread],
        [NSThread isMainThread],
        [weakOperation isAsynchronous]);
}];
 
[operation addExecutionBlock:^() {
  NSLog(@"这是第三个任务在线程:%@执行,isMainThread: %d,isAync: %d",
        [NSThread currentThread],
        [NSThread isMainThread],
        [weakOperation isAsynchronous]);
}];
 
[operation addExecutionBlock:^() {
  NSLog(@"这是第四个任务在线程:%@执行,isMainThread: %d,isAync: %d",
        [NSThread currentThread],
        [NSThread isMainThread],
        [weakOperation isAsynchronous]);
}];
 
// 开始执行任务
[operation start]
 

看看打印结果:

 
1
2
3
4
5
6
 
2015-11-24 17:15:49.489 TestGCD[42401:3307632] 这是第一个任务在线程:<NSThread: 0x7fb369e02c30>{number = 1, name = main}执行,isMainThread: 1,isAync: 0
2015-11-24 17:15:49.489 TestGCD[42401:3307797] 这是第二个任务在线程:<NSThread: 0x7fb369ca8880>{number = 2, name = (null)}执行,isMainThread: 0,isAync: 0
2015-11-24 17:15:49.489 TestGCD[42401:3307809] 这是第三个任务在线程:<NSThread: 0x7fb369ca92b0>{number = 3, name = (null)}执行,isMainThread: 0,isAync: 0
2015-11-24 17:15:49.489 TestGCD[42401:3307798] 这是第四个任务在线程:<NSThread: 0x7fb369f2acd0>{number = 4, name = (null)}执行,isMainThread: 0,isAync: 0
 

由此,我们可以看到第一个任务在主线程执行,第二、三、四个任务都是其它子线程完成的。这四个任务都是同步执行的。其中的number代表线程的id

当我们把[operation start]这行改成这样:

 
1
2
3
4
5
 
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
[queue setMaxConcurrentOperationCount:2];
 

其打印结果如下:

 
1
2
3
4
5
6
 
2015-11-24 17:22:26.206 TestGCD[42558:3316054] 这是第三个任务在线程:<NSThread: 0x7f9000744750>{number = 4, name = (null)}执行,isMainThread: 0,isAync: 0
2015-11-24 17:22:26.206 TestGCD[42558:3316053] 这是第一个任务在线程:<NSThread: 0x7f900061b6f0>{number = 2, name = (null)}执行,isMainThread: 0,isAync: 0
2015-11-24 17:22:26.206 TestGCD[42558:3316055] 这是第二个任务在线程:<NSThread: 0x7f90006183b0>{number = 3, name = (null)}执行,isMainThread: 0,isAync: 0
2015-11-24 17:22:26.206 TestGCD[42558:3316062] 这是第四个任务在线程:<NSThread: 0x7f9000609680>{number = 5, name = (null)}执行,isMainThread: 0,isAync: 0
 

由于我们设置了最大并发数量为2,因此同时能执行的任务数量最多两个。而这四个任务都不是在主线程执行的,全部放到子线程中执行了。我们发现isAync都为0,也就是说operationisAsynchronous方法返回都是NO

注意:并发与异步不是同一个概念

要异步执行,可以这样:

 
1
2
3
4
5
6
7
8
9
 
[operation addExecutionBlock:^() {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"这是第四个任务在线程:%@执行,isMainThread: %d",
          [NSThread currentThread],
          [NSThread isMainThread]);
  });
}];
 

自定义NSOperation


如果NSInvocationOperationNSBlockOperation对象不能满足需求, 我们可以直接继承NSOperation, 并添加额外的功能。继承所需的工作量主要取决于你要实现非并发还是并发的NSOperation。定义非并发的NSOperation要简单许多,只需要重载-main这个方法,在这个方法里面执行主任务,并正确地响应取消事件; 对于并发NSOperation, 必须重写NSOperation的多个基本方法进行实现。

非并发自定义NSOperation

我们定义一个图片下载类来说明如何自定义非并发的NSOperation

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
typedef void(^HYBDownloadReponse)(UIImage *image);
/*!
*  @author 黄仪标, 15-11-24 22:11:50
*
*  下载operation
*/
@interface DownloadOperation : NSOperation
 
@property (nonatomic, copy) NSString *url;
@property (nonatomic, copy) HYBDownloadReponse responseBlock;
 
- (instancetype)initWithUrl:(NSString *)url completion:(HYBDownloadReponse)completion;
 
@end
 

下面看看实现方法怎么实现的,关键点在于-main方法:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 
//
//  DownloadOperation.m
//  TestGCD
//
//  Created by huangyibiao on 15/11/24.
//  Copyright © 2015年 huangyibiao. All rights reserved.
//
 
#import "DownloadOperation.h"
 
@implementation DownloadOperation
 
- (instancetype)initWithUrl:(NSString *)url completion:(HYBDownloadReponse)completion {
  if (self = [super init]) {
    self.url = url;
    self.responseBlock = completion;
  }
  
  return self;
}
 
// 必须重写这个主方法
- (void)main {
  // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
  @autoreleasepool {
    // 如果刚进来,就已经被取消了,则直接退出
    if (self.isCancelled) {
      return;
    }
    
    // 获取图片数据
    NSURL *url = [NSURL URLWithString:self.url];
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    
    // 被取消,也有可能发生在获取数据后
    if (self.isCancelled) {
      url = nil;
      imageData = nil;
      return;
    }
    
    UIImage *image = [UIImage imageWithData:imageData];
    
    // 被取消,也可能发生在转换的地方
    if (self.isCancelled) {
      image = nil;
      return;
    }
    
    if (self.responseBlock) {
      dispatch_async(dispatch_get_main_queue(), ^{
        self.responseBlock(image);
      });
    }
  }
}
 
@end
 

我们测试一下并发:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 
DownloadOperation *operation = [[DownloadOperation alloc] initWithUrl:@"https://mmbiz.qlogo.cn/mmbiz/sia5QxFVcFD0wkCgnmf6DVxI6fVewNS8rhtZb71v2DMpDy8jIdtviaetzicwQzTEoKKyHAN96Beibk2G61tZpezQ0Q/0?wx_fmt=png" completion:^(UIImage *image) {
    self.imageView.image = image;
    NSLog(@"fisrt");
  }];
  DownloadOperation *operation1 = [[DownloadOperation alloc] initWithUrl:@"https://mmbiz.qlogo.cn/mmbiz/sia5QxFVcFD0wkCgnmf6DVxI6fVewNS8rhtZb71v2DMpDy8jIdtviaetzicwQzTEoKKyHAN96Beibk2G61tZpezQ0Q/0?wx_fmt=png" completion:^(UIImage *image) {
    self.imageView1.image = image;
    NSLog(@"second");
  }];
  DownloadOperation *operation2 = [[DownloadOperation alloc] initWithUrl:@"https://mmbiz.qlogo.cn/mmbiz/sia5QxFVcFD0wkCgnmf6DVxI6fVewNS8rhtZb71v2DMpDy8jIdtviaetzicwQzTEoKKyHAN96Beibk2G61tZpezQ0Q/0?wx_fmt=png" completion:^(UIImage *image) {
    self.imageView2.image = image;
    NSLog(@"third");
  }];
  DownloadOperation *operation3 = [[DownloadOperation alloc] initWithUrl:@"https://mmbiz.qlogo.cn/mmbiz/sia5QxFVcFD0wkCgnmf6DVxI6fVewNS8rhtZb71v2DMpDy8jIdtviaetzicwQzTEoKKyHAN96Beibk2G61tZpezQ0Q/0?wx_fmt=png" completion:^(UIImage *image) {
    self.imageView3.image = image;
    NSLog(@"fourth");
  }];
  DownloadOperation *operation4 = [[DownloadOperation alloc] initWithUrl:@"https://mmbiz.qlogo.cn/mmbiz/sia5QxFVcFD0wkCgnmf6DVxI6fVewNS8rhtZb71v2DMpDy8jIdtviaetzicwQzTEoKKyHAN96Beibk2G61tZpezQ0Q/0?wx_fmt=png" completion:^(UIImage *image) {
    self.imageView4.image = image;
    NSLog(@"fifth");
  }];
 
  NSOperationQueue *queue = [[NSOperationQueue alloc]init];
  [queue addOperation:operation];
  [queue addOperation:operation1];
  [queue addOperation:operation2];
  [queue addOperation:operation3];
  [queue addOperation:operation4];
  [queue setMaxConcurrentOperationCount:2];
 

效果如下:

标哥的技术博客

http://www.henishuo.com/ios-nsoperation-queue/

iOS开发多线程--(NSOperation/Queue)的更多相关文章

  1. iOS 开发多线程 —— NSOperation

    本文是根据文顶顶老师的博客学习而来,转载地址:http://www.cnblogs.com/wendingding/p/3809042.html 一.NSOperation简介 1.简单说明 NSOp ...

  2. iOS开发-多线程NSOperation和NSOperationQueue

    上一篇文章稍微提及了一下NSThread的使用,NSThread能直观地控制线程对象,不过需要自己管理线程的生命周期,线程同步,用起来比较繁琐,而且比较容易出错.不过Apple给出了自己的解决方案NS ...

  3. iOS开发多线程篇—NSOperation简单介绍

    iOS开发多线程篇—NSOperation简单介绍 一.NSOperation简介 1.简单说明 NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能实现 ...

  4. iOS开发多线程篇—NSOperation基本操作

    iOS开发多线程篇—NSOperation基本操作 一.并发数 (1)并发数:同时执⾏行的任务数.比如,同时开3个线程执行3个任务,并发数就是3 (2)最大并发数:同一时间最多只能执行的任务的个数. ...

  5. iOS开发多线程篇—自定义NSOperation

    iOS开发多线程篇—自定义NSOperation 一.实现一个简单的tableView显示效果 实现效果展示: 代码示例(使用以前在主控制器中进行业务处理的方式) 1.新建一个项目,让控制器继承自UI ...

  6. iOS开发多线程篇 09 —NSOperation简单介绍

    iOS开发多线程篇—NSOperation简单介绍 一.NSOperation简介 1.简单说明 NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能实现 ...

  7. iOS开发多线程篇 11 —自定义NSOperation

    iOS开发多线程篇—自定义NSOperation 一.实现一个简单的tableView显示效果 实现效果展示: 代码示例(使用以前在主控制器中进行业务处理的方式) 1.新建一个项目,让控制器继承自UI ...

  8. iOS开发多线程篇 10 —NSOperation基本操作

    iOS开发多线程篇—NSOperation基本操作 一.并发数 (1)并发数:同时执⾏行的任务数.比如,同时开3个线程执行3个任务,并发数就是3 (2)最大并发数:同一时间最多只能执行的任务的个数. ...

  9. 【iOS开发】NSOperation简单介绍

    iOS开发多线程篇—NSOperation简单介绍 一.NSOperation简介 1.简单说明 NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能实现 ...

  10. iOS 开发多线程篇—GCD的常见用法

    iOS开发多线程篇—GCD的常见用法 一.延迟执行 1.介绍 iOS常见的延时执行有2种方式 (1)调用NSObject的方法 [self performSelector:@selector(run) ...

随机推荐

  1. ALTERA MAX10官方评估板,新鲜出炉!

    刚刚拿到骏龙提供的ALTERA MAX10官方评估板,还热乎呢,呵呵!赶紧跟大家分享一下 板子很简单,把IO口都扩展出来了,其他功能基本上没有. FPGA型号是10M08SAE144C8GES,144 ...

  2. [uwp开发]数据绑定那些事(1)

    现在是msp候选人,是时候写点技术博客来加分了(实则是个人的心得体会). 注:以下都是个人理解,错误在所难免,欢迎批评指正 以前接触过WPF,只会简单的一些操作,现在在逐渐学习UWP(Universa ...

  3. windows多线程编程(一)(转)

    源出处:http://www.cnblogs.com/TenosDoIt/archive/2013/04/15/3022036.html CreateThread:Windows的API函数(SDK函 ...

  4. 20145129 《Java程序设计》第6周学习总结

    20145129 <Java程序设计>第6周学习总结 教材学习内容总结 InputStream与OutStream 串流设计的概念 输入串流代表对象为java.io.InputStream ...

  5. CoffeeRobotTeam项目组报告

    一.小组分工 模块 任务 责任人 备注 报告 需求分析 熊振威 功能分析 熊振威 项目报告 熊振威 人机界面 秦勤.洪超 单元测试 姜进.张文强 机器人代码 机器人类 徐意.余拥军.孙智博 机器人运动 ...

  6. php中curl的详细解说(转载)

    本文转自:http://blog.csdn.net/yanhui_wei/article/details/21530811 这几天在帮一些同学处理问题的时候,突然发现这些同学是使用file_get_c ...

  7. Android基础整理之四大组件Activity

    最近准备系统的重新整理复习一下Android的各方面的知识,本着知识分享的原则,我就把梳理过程中一些东西给记录下来,权当一个学习笔记吧. 下面步入正题..... 什么是Activity Activit ...

  8. 【Length of Last Word】cpp

    题目: Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return t ...

  9. Entity Framework公共的增删改方法

    using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.I ...

  10. [bzoj 2097]奶牛健美操

    题目描述 对于一棵n个点的树,删除k条边,使得所有联通块直径最大值最小 题解 首先二分联通块直径最大值的最小值. 那么这个能否达成的判定变成了一个类似树形dp的东西 对于一个子树,删除一条边可以删除整 ...