多线程是我们程序开发中不得不面对的问题。iOS开发中主要有三种多线程实现机制:NSThread,NSOperationQueue,GCD,抽象层次分别增高,抽象层越高,使用就越方面。我在前面的5篇博客中《GCD实践——串行队列/并发队列与iOS多线程详解》等讲解了如何使用GCD,今天我们来学习一下NSOperationQueue的使用。本示例代码提交在 https://github.com/chenyufeng1991/NSOperationQueue。
在OS X10.6 和iOS 4之前,NSOperation/NSOperationQueue不同于GCD,他们使用了完全不同的机制。
从OS X10.6 和iOS4之后,NSOPeration和NSOperationQueue是建立在GCD上的。作为惯例,苹果推荐使用最高级别的抽象。
首先了解下什么是NSOperation,NSOperation就是一个操作,准确的说就是一个任务,也相当于一个函数块、block块。然后,任务便会有开始执行(start),取消(cancel),是否取消(isCancel),是否完成(isFinishing),暂停(pause)等状态函数,NSOperation本身就是一个基类,不能直接使用,必须继承它。最重要的是只有被加入到OperationQueue中才会被执行。
NSOperation中比较重要的是start和main函数,一般结合NSOperationQueue来使用。NSOperationQueue是一个队列,我们需要把一个任务加入到一个队列中。OperationQueue实质上也就是数组管理,对添加进去的operation进行创建、取消、执行等操作。添加到queue中的operation,queue会默认调用operation的start函数来执行任务,而start函数默认又是调用main函数的。
定义一个任务的步骤如下:
(1)继承自NSOperation类;
(2)重写main方法;
(3)在main方法中创建一个autoreleasepool;
(4)把你需要的代码放到autoreleasepool中执行;
在线程操作中,我们从来不能确定一个线程什么时候开始,会持续多少时间。下面我通过一段简单地代码演示如何使用NSOperation和NSOperationQueue:
(1)定义一个MyTask类,继承自NSOperation,并声明一个属性,用来标识线程ID,头文件如下:
#import
@interface MyTask : NSOperation
@property(nonatomic,assign) int operationID;
@end
#import "MyTask.h"
@implementation MyTask
- (void)main{
@autoreleasepool {
NSLog(@"task %i 开始 … ",self.operationID);
[NSThread sleepForTimeInterval:3];
NSLog(@"task %i 结束 ",self.operationID);
}
}
@end
#import "ViewController.h"
#import "MyTask.h"
@interface ViewController ()
//声明一个NSOperationQueue队列;
@property(nonatomic,strong) NSOperationQueue *queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = [[NSOperationQueue alloc] init];
MyTask *task = [[MyTask alloc] init];
//设置任务ID为1,区别其他的操作;
task.operationID = 1;
//加入到队列中;
[self.queue addOperation:task];
//创建一个NSOperation对象(任务),放到NSOperationQueue中,也就是放到一个队列中;
MyTask *task02 = [[MyTask alloc] init];
task02.operationID = 2;
[self.queue addOperation:task02];
}
@end
结果1:
结果2:
以上两次截然不同的运行结果印证了我们上述的说法,线程开始的时间是不确定的,并且持续时间也是不一定的。
下面讲解下NSOperation中的主要方法和属性:
开始(start):一般我们不会重写该方法,当我们把一个任务加入到队列后,会自动调用start方法
从属性(dependency):可以让一个任务从属于其他操作。任何任务都可以从属于任意数量的操作。当一个B任务从属于A任务时,即使调用了B任务的start方法,也会等到A执行结束后才开始执行。这个类似于GCD中的线程组或者信号量实现同步机制。
优先级(priority):有时候你可能希望后台运行的任务有较低的优先级,前台任务有较高的优先级,都可以通过priority来设置。方法为:[task setQueuePriority:];
下面讲解NSOperationQueue,NSOperationQueue不需要继承,也不需要重写任何方法,只要进行简单的创建即可。还可以给队列起一个名字。
并发操作:队列和线程是两个不同的概念。一个队列可以有多个线程,每个队列中的操作会在所属的线程中运行。如果我们创建了一个并发队列,然后添加三个操作到里面。队列会把这三个操作分别放到三个不同的线程中,然后让所有操作在各自的线程中并发运行。
NSOperationQueue可以设置并发操作的最大并发数。
添加:一个操作一旦被添加到一个队列中,然后队列就会负责这个操作。所以说,什么时候调用操作的start方法,默认是由队列决定的。
待处理:任何时候你可以知道一个队列哪个操作在里面,并且总共有多少操作在里面。
暂停队列:可以通过setSuspended方法来暂停一个队列。这样会暂停所有在队列中的操作。注意:我们只能暂停一个队列,而不是暂停一个操作。
取消:可以同时取消一个队列中的所有操作,执行方法:cancelAllOperations即可。一个NSOperation对象(操作)可以通过哦isCancelled方法判断自己是否被取消。
在了解了Operation和OperationQueue的基本属性和方法后,我们再来对上述代码进行更新,在代码中尝试使用这些属性。注意内部的注释。ViewController内的代码如下:
#import "ViewController.h"
#import "MyTask.h"
@interface ViewController ()
//声明一个NSOperationQueue队列;
@property(nonatomic,strong) NSOperationQueue *queue;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = [[NSOperationQueue alloc] init];
//给队列起一个名字;
self.queue.name = @"还可以给队列起一个名字";
//设置队列的最大并发数;
[self.queue setMaxConcurrentOperationCount:5];
MyTask *task = [[MyTask alloc] init];
MyTask *task02 = [[MyTask alloc] init];
task.operationID = 1;
[task setQueuePriority:NSOperationQueuePriorityVeryLow];
task02.operationID = 2;
//设置优先级;
[task02 setQueuePriority:NSOperationQueuePriorityVeryHigh];
//设置任务间的从属关系;task02会在task执行完之后才开始执行;当然也可以移除从属关系;
[task02 addDependency:task];
[self.queue addOperation:task];
[self.queue addOperation:task02];
//查看当前队列中的所有任务;
NSArray *operationArr = [self.queue operations];
NSLog(@"队列中的任务:%@",operationArr);
//任务1执行完的回调;
[task setCompletionBlock:^{
NSLog(@"task结束了");
}];
//任务2执行完的回调;
[task02 setCompletionBlock:^{
NSLog(@"task02结束了");
}];
//取消队列中的suoyou任务;
[self.queue cancelAllOperations];
}
@end
下面我写一个使用NSOperationQueue来请求网络图片的实例,里面并没有定义Operation操作,为了方便,而是使用block代码块的方式来作为Operation。如果你想要定义NSOperation对象,可以吧block中的语句放到operation中的main方法中执行。主要学习的是使用线程来请求网络操作,并在主线程中进行更新。代码如下:
/**
* 下面定义一个下载队列;
*/
NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.name = @"下载图片队列";
[downloadQueue addOperationWithBlock:^{
NSURL *url = [[NSURL alloc] initWithString:@"http://gb.cri.cn/mmsource/images/2007/12/13/sw071213011.jpg"];
NSData *data = [[NSData alloc]initWithContentsOfURL:url];
//在主界面更新UI;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = [UIImage imageWithData:data];
}];
}];
温馨提示,在iOS9开发中,网络请求默认使用了https,如果你按原来的方式进行请求,网络请求会失败,并报下面的错:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
。修改方式需要在Info.plist文件中添加下面两个属性:
NSAppTransportSecurity Dictionary 里面再添加:
NSAllowsArbitraryLoads Boolean Yes ,如图:
github主页:https://github.com/chenyufeng1991 。欢迎大家访问!