美文网首页
iOS多线程--NSOperation

iOS多线程--NSOperation

作者: Arthur澪 | 来源:发表于2019-11-22 15:54 被阅读0次

简介

NSOperationNSOperationQueue是苹果提供的一套多线程解决方案,是基于GCD更高一层的封装,完全面向对象。但比GCD 更简单易用、代码可读性更高。

  • 好处:
    可添加完成的代码块complete,在操作完成后执行。
    添加操作之间的依赖关系,方便控制执行顺序。
    设定操作执行的优先级。
    可以取消一个操作的执行。
    使用 KVO 观察执行状态:isExecuteing、isFinished、isCancelled

NSOperation、NSOperationQueue中也有类似的“任务”和“队列”的概念。

1.NSOperation

任务,或操作。即,你在线程中执行的那段代码。NSOperation是一个抽象类,要使用其子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装任务。

2.NSOperationQueue

指用来存放操作的队列。

  • 队列类型
    主队列、自定义队列。
    主队列运行在主线程之上,而自定义队列在后台执行。

  • 任务状态和顺序
    不同于 GCD 中的队列 FIFO(先进先出)的原则。对于添加到NSOperationQueue中的任务,首先进入准备就绪的状态(就绪状态取决于任务之间的依赖关系),然后进入就绪状态的任务的开始执行顺序(而非执行完成的顺序)由任务之间相对的优先级决定。

  • 并发和串行
    任务队列通过设置最大并发操作数maxConcurrentOperationCount来控制并发、串行。

3. NSOperation实现多线程

默认情况下,NSOperation单独使用时,系统同步执行操作。只有配合 NSOperationQueue才能更好地实现异步执行。

  • 使用步骤分为3步
    创建任务:先将需要执行的任务,封装到一个 NSOperation对象中。
    创建队列:即NSOperationQueue 对象。
    将任务加入到队列中:将NSOperation对象添加到 NSOperationQueue对象中。

系统就会自动将NSOperationQueue中的NSOperation 取出来,在新线程中执行操作。

3.1创建NSOperation任务

NSOperation 是个抽象类,不能直接用来封装任务。要使用它的子类来封装任务:NSInvocationOperation、NSBlockOperation
以及自定义的子类。

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
[op start];   // 开始执行
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        //  do some thing...
}]; 
[op start];

此外,可以为任务对象添加额外任务。

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    // do something...
}];

// 添加额外的操作
[op addExecutionBlock:^{
    // do otherthing...
}]; 
  • 在没有使用NSOperationQueue、在主线程中单独使用 NSOperation的子类对象 执行一个任务的情况下,任务是在当前线程执行的,并没有开启新线程。
  • 使用子类 NSBlockOperation,并调用方法 addExecutionBlock:的情况下,如果添加的操作的个数多,就会自动开启新线程。
2.创建NSOperationQueue队列

有两种:主队列、自定义队列。

  • 主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];

凡是添加到主队列中的操作,都会放到主线程中执行。

  • 自定义队列
    添加到这种队列中的操作,就会自动放到子线程中执行;同时包含串行、并发功能。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

queue. maxConcurrentOperationCount = 3;

可设置属性maxConcurrentOperationCount最大并发操作数,决定串行、并发。

属性maxConcurrentOperationCount
= 1; // 串行队列,任务只能按顺序串行执行的
= 2; // 并发队列,>2时,任务是并发执行的,可同时执行多个操作
= 8; // 并发队列
当然这个值不应超过系统限制,即使设置了一个很大的值,系统也会自动调整为 min。

3.将操作加入到队列中

方法addOperation:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
 
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        //.....
    }];
    [op3 addExecutionBlock:^{
        //.....
    }];

    [queue addOperation:op1];  
    [queue addOperation:op3];  

方法addOperationWithBlock:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 添加操作到队列中
    [queue addOperationWithBlock:^{
        //.....
    }];

操作和操作队列、使用步骤和基本使用方法、控制串行/并发执行、

  • 把任务添加到队列中,相当于调用了任务的start方法

NSOperation 依赖

通过依赖,可以很方便的控制任务之间的执行先后顺序。

addDependency:(NSOperation *)op;添加依赖
removeDependency:(NSOperation *)op;移除依赖
NSArray<NSOperation *> *dependencies;
在当前任务开始执行之前,已完成任务对象的数组

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        //.....
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        //.....
    }];

    // 添加依赖
    [op2 addDependency:op1]; //让op2 依赖于 op1。表示先执行op1

    [queue addOperation:op1];
    [queue addOperation:op2];
  • 准备就绪状态
    当一个任务的所有依赖都已经完成时,任务对象通常会进入准备就绪状态,等待执行。

NSOperation 优先级

NSOperation 提供了优先级属性queuePriority。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal。但可以通过setQueuePriority:方法来改变在同一队列中的任务的优先级。

queuePriority属性适用于同一队列中的任务,不同队列的任务不适用。
优先级的取值:
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,(默认)
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8

优先级与依赖

  • 优先级属性queuePriority决定了进入准备就绪状态下的任务之间的开始执行顺序。并且,优先级不能取代依赖关系。
  • 如果一个队列中既包含高优先级任务,又包含低优先级任务,并且两个任务都已经准备就绪,那么队列先执行高优先级任务。
  • 优先级不能取代依赖关系。
    如果,一个队列中既包含了准备就绪状态的任务A,又包含了未准备就绪的任务B,B的优先级比A高。那么,会优先执行B。如果要控制操作间的启动顺序,则必须使用依赖关系。

线程间的通信

处理完子行程的任务,返回主行程处理

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
 
    [queue addOperationWithBlock:^{
        // .....

        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 回到主线程,  进行一些 UI 刷新等操作
        }];
    }];

取消线程

正在执行的操作,无法取消。只能取消未执行的操作。

1.可通过 cancel 方法,取消未执行的单个操作。

NSOperationQueue *queue1 = [[NSOperationQueue alloc]init];
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block11");
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block22");
}];
NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block33");
}];
[block3 cancel];

[queue1 addOperations:@[block1,block2,block3] waitUntilFinished:YES];
  1. cancelAllOperations方法,但无法移除 正在执行的操作。
// 移除队列里面所有的操作
[queue1 cancelAllOperations];

3.挂起队列,使队列任务不再执行,但正在执行的操作无法挂起。

queue1.suspended = YES;

要实现取消正在执行的操作,可以自定义NSOperation,其实就是拦截main方法。
main方法:
1.任何操作在执行时,首先会调用start方法,它会更新操作的状态过滤操作(如过滤掉处于“取消”状态的操作)
2.经start方法过滤后,只有正常可执行的操作,就会调用main方法。
3.重写操作的入口方法(main),就可以在这个方法里面指定操作执行的任务。
4.main方法默认是在子线程异步执行的。

线程同步和线程安全

  • 线程安全
    有多个线程可能会同时运行一段代码。如果每次运行结果和单线程运行的结果一样,且其他变量的值也和预期的是一样的,就是线程安全的。
    若每个线程中对全局变量、静态变量只有读操作(无写操作),一般这个变量是线程安全的。
    若有多个线程同时执行写操作(更改变量的值),就需要考虑线程同步,否则就可能影响线程安全。

  • 线程同步
    可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
    例子:火车票售卖

线程安全解决方案:加锁

可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。

iOS 实现线程加锁有很多种方式:
dispatch_semaphore:建议使用,性能较好
NSLock
@synchronized:性能最差
os_unfair_lock:iOS10开始
NSCondition
NSConditionLock
pthread_mute
OSSpinLock:iOS10 废弃
atomic(property) set/get :原子操作

思想:执行写操作前,上锁,写操作完,解锁。

自旋锁:
会忙等。所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
适用的情况
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张、多核处理器

互斥锁:
会休眠。 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。
适用的情况
预计线程等待锁的时间较长
临界区竞争非常激烈
临界区有IO操作
临界区代码复杂或者循环量大
单核处理器

死锁:
通常是指2个操作相互等待对方完成,造成死循环,于是2个操作都无法进行,就产生了死锁。

常用属性和方法归纳

.

NSOperation 和GCD的对比

  • NSOperation 是对线程的高度抽象,在项目中使 用它,会使项目的程序结构更好。子类化 NSOperation 的设计思路,是具有面向对 象的优点(复用、封装),使得实现是多线程支持,而接口简单。
    建议在复杂项目中 使用。

  • GCD 本身非常简单、易用,对于不复杂的多线程操 作,会节省代码量,而 Block 参数的使用,会是代码更为易读。
    建议在简单项目中 使用。

  • NSOperation的优点

1.NSOperationQueue可以轻松在Operation间设置依赖关系,而GCD 需要写很多代码才能实现。

  1. NSOperation可以很方便地调整执⾏顺序、设置最⼤并发数。
    3.NSOperationQueue⽀支持KVO,可以监测operation的状态:是否正在执行(isExecuted)、 是否结束(isFinished),是否取消(isCanceld)
    4.GCD只⽀支持FIFO的队列列

相关文章

网友评论

      本文标题:iOS多线程--NSOperation

      本文链接:https://www.haomeiwen.com/subject/hxdfkxtx.html