上一篇文章中看到使用自定义NSOperation来实现多线程,写法有些复杂,但其实,使用NSOperationQueue来实现多线程非常简单。在本节中,您将学习几种执行操作的方法以及如何在运行时操纵你的operation的执行。
NSOperationQueue为我们提供了非常简便的使用多线程的方法,如果需要使用NSOperation,则更多建议使用NSOperationQueue而不是自定义NSOperation。
(1)向一个Operation中添加Queue
到目前为止,执行operation的最简单的方法是使用操作队列,它是NSOperationQueue类的一个实例。如果打算使用的操作队列,那么您的应用程序应该负责创建和维护operation queue。 应用程序可以具有任意数量的队列,但是对于在给定时间点可以执行多少操作存在实际限制,因为操作队列与系统一起工作,为了适合可用核心和系统负载的值,所以并发操作的数量有所限制。 因此,创建其他队列并不意味着您可以执行其他操作。
创建operation queue
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
使用addOperation:方法将操作添加到队列中;
使用addOperation:waitUntilFinished:方法将一组操作添加到队列中;
使用addOperationWithBlock:将块对象添加到队列中;
上面的每一种方法都对一个或多个操作进行排队,并通知队列它何时开始处理它们。 在大多数情况下,操作在被添加到队列之后立即就开始执行了,但是操作队列可能由于以下原因延迟队列中的操作的执行:如果排队的操作依赖于尚未完成的其他操作,则可以延迟执行。 如果操作队列本身被挂起或已经执行其最大数量的并发操作,执行也可能被延迟。
以下示例显示了向队列添加操作的基本语法。
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
/* Do something. */
}];
例子:
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue *queue = [NSOperationQueue new];
for (NSUInteger i = 0; i < 3; i ++) {
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"你好" forKey:@"打招呼"];
NSInvocationOperation *inOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationCountSelector: )object:dict];
[queue addOperation:inOp];
}
}
-(void)operationCountSelector:(NSDictionary *)dict{
// 接收传进来的dict
NSLog(@"dictValue = %@", [dict valueForKey:@"打招呼"]);
sleep(3); // 加个睡眠模仿耗时操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
}
输出
-dictValue = 你好
-dictValue = 你好
-dictValue = 你好
-currentThread = <NSThread: 0x7f8de34085f0>{number = 3, name = (null)}
-currentThread = <NSThread: 0x7f8de350abb0>{number = 2, name = (null)}
-currentThread = <NSThread: 0x7f8de3419ac0>{number = 4, name = (null)}
从以上可以看出,将3个operation加入队列,开启三个线程并发执行.
的前提下
重要的:
在将操作对象添加到队列之前,应对操作对象进行必要的配置和修改,因为一旦添加到队列中,操作可能随时运行,如果再配置operation可能会无法达到预期效果。
虽然NSOperationQueue类是为并行执行操作而设计的,但是可以强制单个队列一次只运行一个操作。 setMaxConcurrentOperationCount:方法允许您配置operation queue的最大并发操作数,将1传递给此方法会使队列每次只执行一个操作。虽然一次只能执行一个操作,但是执行的顺序仍然基于其他因素,例如每个操作的准备状态及其分配的优先级。因此,串行化操作队列不提供与Grand Central Dispatch中的串行调度队列完全相同的行为。如果操作对象的执行顺序对您很重要,则应该在将操作添加到队列之前使用依赖关系来建立该顺序。
在创建队列之后添加一下代码
[queue setMaxConcurrentOperationCount:1];
输出
- dictValue = 你好
-currentThread = <NSThread: 0x7f9458713500>{number = 2, name = (null)}
-dictValue = 你好
-currentThread = <NSThread: 0x7f9458609a40>{number = 3, name = (null)}
-dictValue = 你好
-currentThread = <NSThread: 0x7f9458713500>{number = 2, name = (null)}
添加之后,是一条线程执行完,在执行另一条线程。
(2)手动执行操作
虽然操作队列是运行操作对象的最方便的方法,但是也可以在没有队列的情况下执行操作。但是,如果您选择手动执行操作,则应在代码中采取一些预防措施。注意,操作必须准备好运行,并且必须始终使用其start方法启动它。
直到其isReady方法返回“YES”,才认为操作可以开始运行。 isReady方法被集成到NSOperation类的依赖性管理系统中,以提供操作的依赖性的状态。只有当其依赖性被清除时,才可以自由地开始执行。
当手动执行操作时,应始终使用start方法开始执行。您使用此方法,而不是main或其他方法,因为start方法在实际运行您的自定义代码之前执行几个安全检查。特别地,默认的start方法生成操作需要正确处理其依赖关系的KVO通知。如果您的操作已经被取消,并且如果您的操作实际上没有准备好运行,那么此方法也会正确地避免执行您的操作。
如果应用程序定义了并发操作对象,那么在启动之前还应该考虑调用isConcurrent操作方法。如果此方法返回NO,您的本地代码可以决定是在当前线程中同步执行操作还是先创建一个单独的线程,然而,实现这种检查完全取决于你。
手动执行操作代码
- (BOOL)performOperation:(NSOperation*)anOp
{
BOOL ranIt = NO;
if ([anOp isReady] && ![anOp isCancelled])
{
if (![anOp isConcurrent])
[anOp start];
else
[NSThread detachNewThreadSelector:@selector(start)
toTarget:anOp withObject:nil];
ranIt = YES;
}
else if ([anOp isCancelled])
{
// If it was canceled before it was started,
// move the operation to the finished state.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
// Set ranIt to YES to prevent the operation from
// being passed to this method again in the future.
ranIt = YES;
}
return ranIt;
}
Demo:https://github.com/onebutterflyW/NSOperation
(3)其他操作
取消操作
一旦添加到操作队列,操作对象由队列有效拥有,并且无法删除。 退出操作的唯一方法是取消操作。 您可以通过调用其取消方法来取消单个单独的操作对象,也可以通过调用队列对象的cancelAllOperations方法来取消队列中的所有操作对象。
等待操作完成
为了获得最佳性能,您应该尽可能将操作设计为异步,让应用程序在操作执行时自由地执行其他工作。如果创建operation的代码还处理该操作的结果,你可以使用NSOperation的waitUntilFinished方法阻止该代码,直到操作完成。一般来说,最好避免调用这个方法,阻塞当前线程可能是一个方便的解决方案,但它会在代码中引入更多的序列化,并限制了并发的总量。
重要:你应该永远不等待主线程中的的操作。您应该仅从辅助线程或其他操作执行waitUntilFinished操作;因为阻止您的主线程就相当于阻止您的应用程序响应用户事件,并可能导致您的应用程序显示无响应。
除了等待单个操作完成之外,您还可以通过调用NSOperationQueue的waitUntilAllOperationsAreFinished方法等待队列中的所有操作。当等待整个队列完成时,请注意,您的应用程序的其他线程仍然可以向队列添加操作,从而延长等待时间。
暂停和恢复队列
如果要暂时停止执行操作,可以使用setSuspended:方法挂起相应的操作队列。 挂起队列不会导致已经执行的操作在其任务中暂停,它只是阻止新操作被调度执行。 您可以暂停队列以响应用户请求,因为用户最终可能希望恢复该工作。
- (void)viewDidLoad {
[super viewDidLoad];
queue = [NSOperationQueue new];
//[queue setMaxConcurrentOperationCount:1];
for (NSUInteger i = 0; i < 3; i ++) {
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"你好" forKey:@"打招呼"];
NSInvocationOperation *inOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationCountSelector: )object:dict];
[queue addOperation:inOp];
}
//挂起后,在添加操作,添加的操作不执行,直到queue的suspended属性设置为NO。
queue.suspended = YES;
// 然后再添加一个操作
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"hello"forKey:@"打招呼"];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationCountSelector:) object:dict];
[queue addOperation:op];
}
-(void)operationCountSelector:(NSDictionary *)dict{
// 接收传进来的dict
NSLog(@"dictValue = %@", [dict valueForKey:@"打招呼"]);
sleep(3); // 加个睡眠模仿耗时操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
//在suspended改为 NO 之后才开始执行的。
queue.suspended = NO;
}
输出
-dictValue = 你好
-dictValue = 你好
-dictValue = 你好
-currentThread = <NSThread: 0x7f9bc3c09200>{number = 2, name = (null)}
-currentThread = <NSThread: 0x7f9bc3e0c090>{number = 4, name = (null)}
-currentThread = <NSThread: 0x7f9bc3c147e0>{number = 3, name = (null)}
-dictValue = hello
-currentThread = <NSThread: 0x7f9bc3e0c090>{number = 4, name = (null)}
可看出,在suspended改为 NO 之后才开始执行的
demo
https://github.com/onebutterflyW/NSOperation
(4)接下来看其他的属性
// 返回当前队列中的所有操作NSOperation
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
// 返回当前队列中的操作数量,对应 operations.count
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
// 可读写的属性,当设备性能不足或根据需求要限制并行的操作数量时,可以设置这个值。
// 设置了这个值之后,队列中并发执行的操作数量不会大于这个值。超出这个值在排队中的操作会处于休眠状态。
// 默认值为 NSOperationQueueDefaultMaxConcurrentOperationCount = -1
@property NSInteger maxConcurrentOperationCount;
// 可以给队列指定一个名字用来做标识
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
// 给队列指定一个优先级,默认为 NSQualityOfServiceDefault = -1
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
// ??? 这个不是太理解
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
// 取消队列中的所有操作。其实就是调用 operations 中每个操作的`cancel`方法才取消操作。
// 但是,在前面的文章中说过,调用`cancel`方法并不会终止操作,而是设置`cancelled`属性为 YES,
// 这就需要自己在操作中分节点去判断`cancelled`属性了,在适当的时机结束操作。
- (void)cancelAllOperations;
// 调用这个方法时,会判断 NSOperationQueue 中的操作是否全部执行完,如果没有,则调用者所在的线程会在调用处等待。
// 直到 NSOperationQueue 中的所有操作执行完成,当前线程才继续执行。如果 NSOperationQueue 为空,则该方法立刻返回。
- (void)waitUntilAllOperationsAreFinished;
// 取得调用者的当前线程中的 NSOperationQueue 操作队列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
// 取得主线程中的
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
@property (getter=isSuspended) BOOL suspended;