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. C++ 学习笔记(一)

    只是记录自己学习C++ 笔记实例来自<c++ primer> 1.static: static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化.这种对象一旦被创建, ...

  2. 20145129 《Java程序设计》第5周学习总结

    20145129 <Java程序设计>第5周学习总结 教材学习内容总结 语法与继承架构 使用try.catch Java中所有错误都会被打包为对象,可以尝试(try)捕捉(catch)代表 ...

  3. cnblogs用户体验

    在使用博客园的这段时间内,我们感觉有优点也有缺点,下面谈谈我们的看法: 1.是什么样的用户?有什么样的心理?对cnblogs的期望值是什么? 我们是学生用户,使用cnblogs主要是提交作业记录自己的 ...

  4. VLC编译问题

    在Ubuntu下编译VLC源代码生成的VLC无法播放Youtube视频(比如https://www.youtube.com/watch?v=mDp-ABzpRX8) 错误提示如下: zlf@ubunt ...

  5. Django RequestContext用法

    模版中的变量由context中的值来替换,如果在多个页面模版中含有相同的变量,比如:每个页面都需要{{user}},笨办法就是在每个页面的请求视图中都把user放到context中.   from d ...

  6. Codeforces Round #350 (Div. 2) E. Correct Bracket Sequence Editor 模拟

    题目链接: http://codeforces.com/contest/670/problem/E 题解: 用STL的list和stack模拟的,没想到跑的还挺快. 代码: #include<i ...

  7. Leetcode#49 Anagrams

    原题地址 Anagram:变位词.两个单词是变位词关系的条件是:组成单词的字符相同,只是顺序不同 第一次看这道题看了半天没明白要干嘛,丫就不能给个样例输入输出么..后来还是看网上其他人的总结知道是怎么 ...

  8. 了解javascript中的事件(一)

    本人目录如下: 零.寒暄 一.事件概念 二.事件流 三.事件处理程序 四.总结 零.寒暄 由于刚入职,近期事情繁多,今天好不容易中期答辩完事,晚上有一些时间,来给大家分享一篇博文. 这段时间每天写js ...

  9. php正则过滤html标签、空格、换行符的代码,提取图片

    $descclear = str_replace("r","",$descclear);//过滤换行 $descclear = str_replace(&quo ...

  10. Post 的数据被截断

    原因: Form 域 POST 提交数据 100K(可能不是这个值) 限制的解决方案   因为微软这个限制是对表单内每个域(第一个控件)的限制.问题的解决办法是,对于一个需要发送大数据的域,在提交表单 ...