多线程 NSThread,NSOperation,GCD

iOS中的线程编程

 

iOS主线程(UI线程),咱们的大部分业务逻辑代码运行于主线程中。 没有特殊需求,不该引入线程增长程序复杂度。 应用场景:逻辑执行时间过长,严重影响交互体验(界面卡死)等。服务器

iOS多线程 有三种主要方法(1)NSThread(2)NSOperation(3)GCD网络

下面简单介绍这三个方法多线程

1.NSThread并发

调用方法以下:异步

如函数须要输入参数,可从object传进去。函数

( 1 ) [ N S T h r e a d  d e t a c h N e w T h r e a d S e l e c t o r : @ s e l e c t o r ( t h r e a d In M a i n M e t h o d : ) t o T a r g e t : s e l f w i t h Object:nil];性能

(2) NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadInM ainMethod:) object:nil];测试

[myThread start];
(3) [obj performSelectorInBackground:@selector(threadMe) withObject:nil];
atom

提个问题:若是某个ViewController里运行了一个Thread,Thread还没结束的时候,这个ViewC ontrollerRelease了,结果会如何?

通过的的测试,Thread不结束,ViewController一直保留,不会执行dealloc方法。

 

Demo源代码:

 

2.NSOperation

Demo源代码:

 

 

 

 

本文目录

  • 前言
  • 1、NSInvocationOperation
  • 2、NSBlockOperation
  • 3、NSOperation的其余用法
  • 4、自定义NSOperation

回到顶部

 

前言

NSOperation有三种状态

isReady -> isExecution -> isFinish

  • isReady: 返回 YES 表示操做已经准备好被执行, 若是返回NO则说明还有其余没有先前的相关步骤没有完成。
  • isExecuting: 返回YES表示操做正在执行,反之则没在执行。
  • isFinished : 返回YES表示操做执行成功或者被取消了

NSOperationQueue只有当它管理的全部操做的isFinished属性全标为YES之后操做才中止出列,也就是队列中止运行,因此正确实现这个方法对于避免死锁很关键。

 

1.上一讲简单介绍了NSThread的使用,虽然也能够实现多线程编程,可是须要咱们去管理线程的生命周期,还要考虑线程同步、加锁问题,形成一些性能上的开销。咱们也能够配合使用NSOperation和NSOperationQueue实现多线程编程,实现步骤大体是这样的:

1> 先将须要执行的操做封装到一个NSOperation对象中

2> 而后将NSOperation对象添加到NSOperationQueue中

3> 系统会自动将NSOperation中封装的操做放到一条新线程中执行

在此过程当中,咱们根本不用考虑线程的生命周期、同步、加锁等问题

下面列举一个应用场景,好比微博的粉丝列表:

每一行的头像确定要重新浪服务器下载图片后才能显示的,并且是须要异步下载。这时候你就能够把每一行的图片下载操做封装到一个NSOperation对象中,上面有6行,因此要建立6个NSOperation对象,而后添加到NSOperationQueue中,分别下载不一样的图片,下载完毕后,回到对应的行将图片显示出来。

 

 

2.默认状况下,NSOperation并不具有封装操做的能力,必须使用它的子类,使用NSOperation子类的方式有3种:

1> NSInvocationOperation

2> NSBlockOperation

3> 自定义子类继承NSOperation,实现内部相应的方法

这讲先介绍如何用NSOperation封装一个操做,后面再结合NSOperationQueue来使用。

 

回到顶部

1、NSInvocationOperation

1 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"mj"] autorelease]; 2 [operation start];

* 第1行初始化了一个NSInvocationOperation对象,它是基于一个对象和selector来建立操做

* 第2行调用了start方法,紧接着会立刻执行封装好的操做,也就是会调用self的run:方法,而且将@"mj"做为方法参数

* 这里要注意:默认状况下,调用了start方法后并不会开一条新线程去执行操做,而是在当前线程同步执行操做。只有将operation放到一个NSOperationQueue中,才会异步执行操做。

 

回到顶部

2、NSBlockOperation

1.同步执行一个操做

1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
2 NSLog(@"执行了一个新的操做"); 3 }]; 4 // 开始执行任务 5 [operation start];

* 第1行初始化了一个NSBlockOperation对象,它是用一个Block来封装须要执行的操做

* 第2行调用了start方法,紧接着会立刻执行Block中的内容

* 这里仍是在当前线程同步执行操做,并无异步执行

 

2.并发执行多个操做

 

 1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
 2   NSLog(@"执行第1次操做,线程:%@", [NSThread currentThread]);  3 }];  4  5 [operation addExecutionBlock:^() {  6   NSLog(@"又执行了1个新的操做,线程:%@", [NSThread currentThread]);  7 }];  8  9 [operation addExecutionBlock:^() { 10   NSLog(@"又执行了1个新的操做,线程:%@", [NSThread currentThread]); 11 }]; 12 13 [operation addExecutionBlock:^() { 14   NSLog(@"又执行了1个新的操做,线程:%@", [NSThread currentThread]); 15 }]; 16 17 // 开始执行任务 18 [operation start];

 

* 第1行初始化了一个NSBlockOperation对象

* 分别在第五、九、13行经过addExecutionBlock:方法添加了新的操做,包括第1行的操做,一共封装了4个操做

* 在第18行调用start方法后,就会并发地执行这4个操做,也就是会在不一样线程中执行

1 2013-02-02 21:38:46.102 thread[4602:c07] 又执行了1个新的操做,线程:<NSThread: 0x7121d50>{name = (null), num = 1} 2 2013-02-02 21:38:46.102 thread[4602:3f03] 又执行了1个新的操做,线程:<NSThread: 0x742e1d0>{name = (null), num = 5} 3 2013-02-02 21:38:46.102 thread[4602:1b03] 执行第1次操做,线程:<NSThread: 0x742de50>{name = (null), num = 3} 4 2013-02-02 21:38:46.102 thread[4602:1303] 又执行了1个新的操做,线程:<NSThread: 0x7157bf0>{name = (null), num = 4}

能够看出,每一个操做所在线程的num值都不同,说明是不一样线程

 

回到顶部

3、NSOperation的其余用法

1.取消操做

operation开始执行以后, 默认会一直执行操做直到完成,咱们也能够调用cancel方法中途取消操做

[operation cancel];

 

2.在操做完成后作一些事情

若是想在一个NSOperation执行完毕后作一些事情,就调用NSOperation的setCompletionBlock方法来设置想作的事情

operation.completionBlock = ^() {
    NSLog(@"执行完毕"); };

当operation封装的操做执行完毕后,就会回调Block里面的内容

 

 NSOperation进阶

优先级

跟NSThread同样,NSOpertion也能够设置优先级。

@property NSOperationQueuePriority queuePriority;

执行顺序(依赖)

有些时候想要控制执行顺序,使用NSOpreation会方便多了,使用NSOpreation的Dependency就能够实现这种功能。

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"excute operation2");        
  }];
  NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"excute operation1");
  }];
  [ope0ration2 addDependency:ope0ration1];
  [queue addOperation:ope0ration1];    
  [queue addOperation:ope0ration2];

上面先执行第一个operation1,等operation1返回isFinish为YES,即operation1完成了,才会执行operation2。

注意死锁:必定不能够循环依赖,像A依赖B,B依赖A,必定不要这样作

CompletionBlock

这个比较容易理解,就是每一个NSOperation执行完毕以后,就会执行该block

NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"执行操做");
}];
[operation setCompletionBlock:^{
    NSLog(@"执行操做完成");
}];
[queue addOperation:operation];

执行结果

2015-09-22 23:47:47.640 Thread Learn[21307:662442] 执行操做
2015-09-22 23:47:47.640 Thread Learn[21307:662482] 执行操做完成

取消

如前面所说,NSOperation有三种状态,isReady -> isExecuting -> isFinish, 若是在Ready的状态中对NSOperation进行取消,NSOperation会进入Finish状态。可是Operation已经开始执行了,就会一直运行到结束,或者由咱们进行显示取消。也就是说Operation已经在executing状态,咱们调用cancle方法系统不会停止线程的,这须要咱们在任务过程当中检测取消事件,并停止线程的执行,还要注意一点咱们要释放内存或资源。仍是看一下实例代码:

- (IBAction)startNSOperation:(id)sender {

    self.blockOperation = [NSBlockOperation blockOperationWithBlock:^{

    if ([self.blockOperation isCancelled]) {
        NSLog(@"取消了");
        return;
    }
    //若是检测还没取消
    //TODO:这里请求网络,获取数据..


    if ([self.blockOperation isCancelled]) {
        NSLog(@"取消了");
        return;
    }    
    //若是检测还没取消
    //TODO:获取到了数据刷新界面...
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:self.blockOperation];
}

 

这种取消跟NSThread有点类似,调用cancle不会退出线程,须要你自已去停止线程,再exit;

回到顶部

4、自定义NSOperation

若是NSInvocationOperation和NSBlockOperation不能知足需求,咱们能够直接新建子类继承NSOperation,并添加任何须要执行的操做。若是只是简单地自定义NSOperation,只须要重载-(void)main这个方法,在这个方法里面添加须要执行的操做。

 

下面写个子类DownloadOperation来下载图片

 

不支持并发

1.继承NSOperation,重写main方法

DownloadOperation.h

 

#import <Foundation/Foundation.h>
@protocol DownloadOperationDelegate; @interface DownloadOperation : NSOperation // 图片的url路径 @property (nonatomic, copy) NSString *imageUrl; // 代理 @property (nonatomic, assign) id<DownloadOperationDelegate> delegate; - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate; @end // 图片下载的协议 @protocol DownloadOperationDelegate <NSObject> - (void)downloadFinishWithImage:(UIImage *)image; @end

 

DownloadOperation.m

 

 1 #import "DownloadOperation.h"  2  3 @implementation DownloadOperation  4 @synthesize delegate = _delegate;  5 @synthesize imageUrl = _imageUrl;  6  7 // 初始化  8 - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate {  9 if (self = [super init]) { 10 self.imageUrl = url; 11 self.delegate = delegate; 12 } 13 return self; 14 } 15 // 释放内存 16 - (void)dealloc { 17 [super dealloc]; 18 [_imageUrl release]; 19 } 20 21 // 执行主任务 22 - (void)main { 23 // 新建一个自动释放池,若是是异步执行操做,那么将没法访问到主线程的自动释放池 24 @autoreleasepool { 25 // .... 26 } 27 } 28 @end

 

* 在第22行重载了main方法,等会就把下载图片的代码写到这个方法中

* 若是这个DownloadOperation是在异步线程中执行操做,也就是说main方法在异步线程调用,那么将没法访问主线程的自动释放池,因此在第24行建立了一个属于当前线程的自动释放池

 

2.正确响应取消事件

* 默认状况下,一个NSOperation开始执行以后,会一直执行任务到结束,就好比上面的DownloadOperation,默认会执行完main方法中的全部代码。

* NSOperation提供了一个cancel方法,能够取消当前的操做。

* 若是是自定义NSOperation的话,须要手动处理这个取消事件。好比,一旦调用了cancel方法,应该立刻终止main方法的执行,并及时回收一些资源。

* 处理取消事件的具体作法是:在main方法中按期地调用isCancelled方法检测操做是否已经被取消,也就是说是否调用了cancel方法,若是返回YES,表示已取消,则当即让main方法返回。

* 如下地方可能须要调用isCancelled方法:

  • 在执行任何实际的工做以前,也就是在main方法的开头。由于取消可能发生在任什么时候候,甚至在operation执行以前。
  • 执行了一段耗时的操做以后也须要检测操做是否已经被取消

 

 1 - (void)main {  2 // 新建一个自动释放池,若是是异步执行操做,那么将没法访问到主线程的自动释放池  3 @autoreleasepool {  4 if (self.isCancelled) return;  5  6 // 获取图片数据  7 NSURL *url = [NSURL URLWithString:self.imageUrl];  8 NSData *imageData = [NSData dataWithContentsOfURL:url];  9 10 if (self.isCancelled) { 11 url = nil; 12 imageData = nil; 13 return; 14 } 15 16 // 初始化图片 17 UIImage *image = [UIImage imageWithData:imageData]; 18 19 if (self.isCancelled) { 20 image = nil; 21 return; 22 } 23 24 if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) { 25 // 把图片数据传回到主线程 26 [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO]; 27 } 28 } 29 }

 

* 在第4行main方法的开头就先判断operation有没有被取消。若是被取消了,那就没有必要往下执行了

* 通过第8行下载图片后,在第10行也须要判断操做有没有被取消

* 总之,执行了一段比较耗时的操做以后,都须要判断操做有没有被取消

* 图片下载完毕后,在第26行将图片数据传递给了代理(delegate)对象

     

支持并发 

自定义并发的NSOperation就麻烦多了,须要实现如下方法,咱们能够看一下下面这个表(来自苹果官方):

 

 

3.GCD

见单独一篇文章

相关文章
相关标签/搜索