前言
本来想对NSOperation进行比较详细的说明,但是刚好看到一篇文章,介绍的比较好这里我就不再去介绍NSOperation,对于NSOperation的介绍可以参考:
这边我通过一些例子和实战演练来介绍NSOperation:
介绍和优点
NSOperation是一个抽象类,不能直接使用它,而是使用系统定义的子类NSInvocationOperation
, NSBlockOperation
执行实际任务。尽管是抽象的,但基本实现确实包含了协调安全执行任务的重要逻辑。这种内置逻辑的存在使您可以专注于任务的实际实现,而不是确保它与其他系统对象正常工作所需的粘合代码。
NSOperation是对GCD的封装的面向对象,因此比起GCD的灵活性,封装后使用上可能相对方便,缺也有不少的局限性。
那么具体有哪些优点呢?
1、确定执行的顺序
NSOperation与GCD一样,都可以增加优先级queuePriority
,会影响到出列和执行block的顺序,但是影响的只是执行顺序开始而已,并不代表执行顺序结束会按照优先级来。比如,两个人吃苹果,你优先级高, 你先开始吃,我后面吃,但是不代表你比我早吃完,
这就表示,优先级只能用来对非依赖性的操作进行分类,而不是用来对不同对象之间的依赖关系管理。其优先级如下:默认NSOperationQueuePriorityNormal
NSOperationQueuePriorityVeryLow
NSOperationQueuePriorityLow
NSOperationQueuePriorityNormal
NSOperationQueuePriorityHigh
NSOperationQueuePriorityVeryHigh
NSOperation提供给用户管理队列执行依赖顺序的方式addOperation
,用此方法添加依赖关系。就可以确保来任务执行顺序。例如我依赖你,你先吃苹果,你吃完我才开始吃苹果。
注意: addOperation的对象执行的时候,不能再被依赖的对象执行之前。同时也不能添加相互依赖,否则会造成死锁。
例如下代码,operation1 依赖于operation2 但是operation1 被先执行,直接崩溃
NSOperationQueue *queue1 = [NSOperationQueue new];
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];
[operation1 addDependency:operation2];
[operation1 start];
[operation2 start];
例如下代码,两个之间添加相互依赖不管谁先执行都会死锁。
NSOperationQueue *queue1 = [NSOperationQueue new];
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];
[operation1 addDependency:operation2];
[operation2 addDependency:operation1];
[operation1 start];
[operation2 start];
2、响应取消命令
Operation完成任务并不意味着完成了任务,操作也能取消。取消操作对象会将对象留在队列中,但会通知对象它应该尽快停止其任务。对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止它正在执行的操作,并将自己标记为已完成。对于排队但尚未执行的操作,队列仍必须调用操作对象的start
方法,以便它可以处理取消事件并将自身标记为已完成。
取消操作不会立即强制它停止正在进行的操作。开发人员自己去判断isCancelled
并根据需要中止。默认实现NSOperation包括检查取消。例如,如果在start调用其方法之前取消操作,则该start方法将退出而不启动任务。
举几个例子,当还没开始执行时,取消操作会直接不执行:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"开始");
NSOperationQueue *queue1 = [NSOperationQueue new];
_operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
[queue1 addOperation:_operation1];
[_operation1 cancel];
NSLog(@"结束");
}
- (void)test {
if (_operation1.isCancelled) {
NSLog(@"取消了");
} else {
NSLog(@"1 当前的线程为--%@", [NSThread currentThread]);
}
}
2019-04-25 15:54:52.371154+0800 test_1[23375:1924354] 开始
2019-04-25 15:54:54.372595+0800 test_1[23375:1924354] 结束
当正在进行中的操作时,取消操作需要手动结束
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"开始");
NSOperationQueue *queue1 = [NSOperationQueue new];
_operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
[queue1 addOperation:_operation1];
sleep(2);
[_operation1 cancel];
NSLog(@"结束");
}
- (void)test {
sleep(3);
if (_operation1.isCancelled) {
NSLog(@"取消了");
} else {
NSLog(@"1 当前的线程为--%@", [NSThread currentThread]);
}
}
2019-04-25 15:54:52.371154+0800 test_1[23375:1924354] 开始
2019-04-25 15:54:54.372595+0800 test_1[23375:1924354] 结束
2019-04-25 15:54:55.376668+0800 test_1[23375:1924399] 取消了
3、符合KVO的属性
NSOperation的属性与KVO和KVC兼容并有其性质。这就表示,这些属性不管在哪个线程都是实时变化的,又KVO通知其变化并改变状态。根据用户的操作和系统运行。让用户可以对线程的变化做响应处理。
isCancelled - 只读
// 让用户知道被请求的操作的取消,需要用户手动处理
isAsynchronous - 只读
// 是否是异步执行
isExecuting - 只读
// 让用户了解操作是否正在积极分配给它的任务。
isFinished - 只读
// 让用户知道操作对象成功完成了它的任务或遭到取消,正在退出。在key更改为之前,操作对象不会清除依赖关系。
// 因此在该值为true之前对于保持队列不通过正在进行或取消的操作进行备份至关重要。
isReady - 只读
// 让用户知道,当一个操作已准备好执行。该属性包含操作现在可以立即执行的值,或者是否仍有未完成的操作依赖于该操作。
dependencies - 只读
// 在当前对象开始执行之前必须完成执行的操作对象数组。
queuePriority - 可读和可写
// 队列等级
completionBlock - 可读和可写
// 操作主要任务完成后执行的块。
线程之间的通信和同步
同步和异步线程
NSOperation子类NSInvocationOperation
, NSBlockOperation
,本身是不具备开辟线程的能力,并且只执行一次。
NSLog(@"开始");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前的线程为--%@", [NSThread currentThread]);
}];
[operation start];
NSLog(@"结束");
2019-04-25 16:38:10.577710+0800 test_1[23677:1936783] 开始
2019-04-25 16:38:10.577894+0800 test_1[23677:1936783] 当前的线程为--<NSThread: 0x280b41a80>{number = 1, name = main}
2019-04-25 16:38:10.577912+0800 test_1[23677:1936783] 结束
NSLog(@"开始");
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
[operation start];
NSLog(@"结束");
2019-04-25 16:39:12.363320+0800 test_1[23704:1937254] 开始
2019-04-25 16:39:12.363537+0800 test_1[23704:1937254] 当前的线程为--<NSThread: 0x280eb9a80>{number = 1, name = main}
2019-04-25 16:39:12.363556+0800 test_1[23704:1937254] 结束
两种方式结果是一样的,都是在主线程执行,并且是串行的方式。
想要实现开辟新线程的能力,则需要与队列NSOperationQueue
结合使用。
NSLog(@"开始");
NSOperationQueue *queue1 = [NSOperationQueue new];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前的线程为--%@", [NSThread currentThread]);
}];
[queue1 addOperation:operation];
NSLog(@"结束");
2019-04-25 16:43:22.327504+0800 test_1[23752:1938565] 开始
2019-04-25 16:43:22.327656+0800 test_1[23752:1938565] 结束
2019-04-25 16:43:22.327763+0800 test_1[23752:1938650] 当前的线程为--<NSThread: 0x2823ecd00>{number = 3, name = (null)}
使用 addExecutionBlock 和 maxConcurrentOperationCount
NSBlockOperation提供一个方式addExecutionBlock
,可以用这种方式可以实现线程安全,即能让程序在线程中串行执行的同时又有开辟新线程的能力:
NSLog(@"开始");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
}];
[operation start];
NSLog(@"结束");
2019-04-25 16:44:47.825744+0800 test_1[23773:1939191] 开始
2019-04-25 16:44:47.825935+0800 test_1[23773:1939191] 1 当前的线程为--<NSThread: 0x283bdee80>{number = 1, name = main}
2019-04-25 16:44:47.825953+0800 test_1[23773:1939235] 2 当前的线程为--<NSThread: 0x283bbc380>{number = 3, name = (null)}
2019-04-25 16:44:47.825961+0800 test_1[23773:1939191] 3 当前的线程为--<NSThread: 0x283bdee80>{number = 1, name = main}
2019-04-25 16:44:47.825981+0800 test_1[23773:1939235] 4 当前的线程为--<NSThread: 0x283bbc380>{number = 3, name = (null)}
2019-04-25 16:44:47.826010+0800 test_1[23773:1939191] 结束
使用 addOperationWithBlock 和 maxConcurrentOperationCount
NSOperationQueue 提供了方法addOperationWithBlock
可以用来实现异步队列,提供了maxConcurrentOperationCount
用来设置最大并发数,通过这些可以实现GCD的并发请求的控制。
NSLog(@"开始");
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSLog(@"结束");
同步队列
我们就以NSBlockOperation
方式为例来实现,实现串行同步。
NSLog(@"开始");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
[operation start];
[operation2 start];
[operation3 start];
[operation4 start];
NSLog(@"结束");
2019-04-25 17:21:09.496324+0800 test_1[24021:1954586] 开始
2019-04-25 17:21:09.496545+0800 test_1[24021:1954586] 1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496579+0800 test_1[24021:1954586] 1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496599+0800 test_1[24021:1954586] 1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496615+0800 test_1[24021:1954586] 1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496625+0800 test_1[24021:1954586] 结束
串行异步
同样的使用NSOperationQueue 来实现异步方式。
第一种方式:
NSLog(@"开始");
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 0 当前的线程为--%@", [NSThread currentThread]);
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
}];
[operation start];
[operation2 start];
[operation3 start];
[operation4 start];
}];
[queue addOperation:op];
NSLog(@"结束");
2019-04-25 17:30:43.933648+0800 test_1[24080:1958061] 开始
2019-04-25 17:34:04.869315+0800 test_1[24095:1959001] 结束
2019-04-25 17:30:43.933959+0800 test_1[24080:1958076] 0 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934065+0800 test_1[24080:1958076] 1 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934100+0800 test_1[24080:1958076] 2 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934118+0800 test_1[24080:1958076] 3 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934134+0800 test_1[24080:1958076] 4 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
第二种方式通过addOperationWithBlock
,并且控制maxConcurrentOperationCount
最大并发数为1
NSLog(@"开始");
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
for (int i=0; I<5; i++) {
[queue addOperationWithBlock:^{
NSLog(@" %d 当前的线程为--%@",i , [NSThread currentThread]);
}];
}
NSLog(@"结束");
2019-04-26 11:33:45.983327+0800 test_1[27906:2321076] 开始
2019-04-26 11:33:45.983518+0800 test_1[27906:2321076] 结束
2019-04-26 11:33:45.983595+0800 test_1[27906:2321143] 0 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983700+0800 test_1[27906:2321143] 1 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983738+0800 test_1[27906:2321143] 2 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983761+0800 test_1[27906:2321143] 3 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983785+0800 test_1[27906:2321143] 4 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
第三种方式,利用NSOperationQueue
和addDependency
相互依赖的方式,但是这种方式虽然是异步串行缺不一定在同一个异步线程中:
NSLog(@"开始");
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
}];
[operation4 addDependency:operation3];
[operation3 addDependency:operation2];
[operation2 addDependency:operation];
[queue addOperation:operation];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
NSLog(@"结束");
2019-04-26 11:40:32.095244+0800 test_1[27947:2323900] 开始
2019-04-26 11:40:32.095411+0800 test_1[27947:2323900] 结束
2019-04-26 11:40:32.095888+0800 test_1[27947:2323953] 1 当前的线程为--<NSThread: 0x2838f7300>{number = 3, name = (null)}
2019-04-26 11:40:32.096086+0800 test_1[27947:2323955] 2 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
2019-04-26 11:40:32.096119+0800 test_1[27947:2323955] 3 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
2019-04-26 11:40:32.096144+0800 test_1[27947:2323955] 4 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
并行异步
通过队列NSOperationQueue 将多个Operation实现并行:
NSLog(@"开始");
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
}];
[queue addOperation:operation];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
NSLog(@"结束");
2019-04-25 17:37:21.282807+0800 test_1[24110:1959919] 开始
2019-04-25 17:37:21.282996+0800 test_1[24110:1959919] 结束
2019-04-25 17:37:21.283182+0800 test_1[24110:1959950] 1 当前的线程为--<NSThread: 0x28133a940>{number = 3, name = (null)}
2019-04-25 17:37:21.283216+0800 test_1[24110:1959949] 4 当前的线程为--<NSThread: 0x28131c6c0>{number = 5, name = (null)}
2019-04-25 17:37:21.283221+0800 test_1[24110:1959948] 2 当前的线程为--<NSThread: 0x281338800>{number = 6, name = (null)}
2019-04-25 17:37:21.283243+0800 test_1[24110:1959951] 3 当前的线程为--<NSThread: 0x28133a900>{number = 4, name = (null)}
如果使用addOperationWithBlock
,并且取消掉最大并发数maxConcurrentOperationCount
,或者设置最大并发数大于1,则就是并行队列了。
NSLog(@"开始");
NSOperationQueue *queue = [NSOperationQueue new];
for (int i=0; i<20; i++) {
[queue addOperationWithBlock:^{
NSLog(@" %d 当前的线程为--%@",i , [NSThread currentThread]);
}];
}
NSLog(@"结束");
2019-04-25 17:37:21.282807+0800 test_1[24110:1959919] 开始
2019-04-25 17:37:21.282996+0800 test_1[24110:1959919] 结束
2019-04-25 17:37:21.283182+0800 test_1[24110:1959950] 1 当前的线程为--<NSThread: 0x28133a940>{number = 3, name = (null)}
2019-04-25 17:37:21.283216+0800 test_1[24110:1959949] 4 当前的线程为--<NSThread: 0x28131c6c0>{number = 5, name = (null)}
2019-04-25 17:37:21.283221+0800 test_1[24110:1959948] 2 当前的线程为--<NSThread: 0x281338800>{number = 6, name = (null)}
2019-04-25 17:37:21.283243+0800 test_1[24110:1959951] 3 当前的线程为--<NSThread: 0x28133a900>{number = 4, name = (null)}
异步执行回到主线程更新UI
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
// 异步进行耗时操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 进行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}];
2019-04-26 17:53:20.178591+0800 test_1[819:39160] 1---<NSThread: 0x282ebc6c0>{number = 3, name = (null)}
2019-04-26 17:53:22.184004+0800 test_1[819:39160] 1---<NSThread: 0x282ebc6c0>{number = 3, name = (null)}
2019-04-26 17:53:24.185889+0800 test_1[819:39134] 2---<NSThread: 0x282ef2e80>{number = 1, name = main}
2019-04-26 17:53:26.187373+0800 test_1[819:39134] 2---<NSThread: 0x282ef2e80>{number = 1, name = main}