子类化NSOperation是并发的和可取消的

我无法find有关如何将NSOperation子类NSOperation并发的良好文档,也支持取消。 我读了苹果文档,但是我找不到一个“官方”的例子。

这是我的源代码:

 @synthesize isExecuting = _isExecuting; @synthesize isFinished = _isFinished; @synthesize isCancelled = _isCancelled; - (BOOL)isConcurrent { return YES; } - (void)start { /* WHY SHOULD I PUT THIS ? if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; return; } */ [self willChangeValueForKey:@"isExecuting"]; _isExecuting = YES; [self didChangeValueForKey:@"isExecuting"]; if (_isCancelled == YES) { NSLog(@"** OPERATION CANCELED **"); } else { NSLog(@"Operation started."); sleep(1); [self finish]; } } - (void)finish { NSLog(@"operationfinished."); [self willChangeValueForKey:@"isExecuting"]; [self willChangeValueForKey:@"isFinished"]; _isExecuting = NO; _isFinished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; if (_isCancelled == YES) { NSLog(@"** OPERATION CANCELED **"); } } 

在我发现的例子中,我不明白为什么使用performSelectorOnMainThread:。 这会阻止我的操作同时运行。

另外,当我注释掉这一行时,我将同时运行我的操作。 但是, isCancelled标志不会被修改,即使我已经调用cancelAllOperations

好,据我了解,你有两个问题:

  1. 你需要代码中的注释中出现的performSelectorOnMainThread:段吗? 这个代码是做什么的?

  2. 为什么在调用包含此操作的NSOperationQueue上的cancelAllOperations时, _isCancelled标志未被修改?

让我们按顺序处理。 我将假设你的NSOperation的子类被称为MyOperation ,只是为了便于解释。 我会解释你的误解,然后给出一个更正的例子。

1.同时运行NSOperations

大多数时候,你会用NSOperation来使用NSOperationQueue ,在你的代码中,这听起来就是你正在做的事情。 在这种情况下, MyOperation将始终在后台线程上运行,而不pipe-(BOOL)isConcurrent方法返回的是什么,因为NSOperationQueue被明确devise为在后台运行操作。

因此,您通常不需要重写-[NSOperation start]方法,因为默认情况下它只是调用-main方法。 这是你应该重写的方法。 默认的启动方法已经在适当的时候处理了isExecutingisFinished

所以,如果你想NSOperation在后台运行,只需重写NSOperationQueue方法并将其放在NSOperationQueue

代码中的performSelectorOnMainThread:会导致MyOperation每个实例始终在主线程上执行其任务。 由于一次只能在一个线程上运行一段代码,这意味着没有其他MyOperation可以运行。 NSOperationNSOperationQueue的全部目的是在后台做一些事情。

唯一一次你想强制主线程的东西是当你更新用户界面。 如果您需要在MyOperation完成时更新UI,那么应该使用performSelectorOnMainThread: 我将在下面的例子中演示如何做到这一点。

2.取消NSOperation

-[NSOperationQueue cancelAllOperations]调用-[NSOperation cancel]方法,导致后续调用-[NSOperation isCancelled]返回YES但是 ,你已经做了两件事情,使这个无效。

  1. 您正在使用@synthesize isCancelled来覆盖NSOperation的-isCancelled方法。 没有理由这样做。 NSOperation已经以完全可以接受的方式实现了。

  2. 您正在检查自己的_isCancelled实例variables以确定操作是否已被取消。 NSOperation保证如果操作被取消, [self isCancelled]将返回YES 。 它不保证你的自定义setter方法将被调用,也不保证你自己的实例variables是最新的。 你应该检查[self isCancelled]

你应该做什么

标题:

 // MyOperation.h @interface MyOperation : NSOperation { } @end 

并执行:

 // MyOperation.m @implementation MyOperation - (void)main { if ([self isCancelled]) { NSLog(@"** operation cancelled **"); } // Do some work here NSLog(@"Working... working....") if ([self isCancelled]) { NSLog(@"** operation cancelled **"); } // Do any clean-up work here... // If you need to update some UI when the operation is complete, do this: [self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO]; NSLog(@"Operation finished"); } - (void)updateButton { // Update the button here } @end 

请注意,您不需要对isExecutingisCancelledisFinished执行任何操作。 这些都是为你自动处理的。 只需重写-main方法。 这很容易。

(注意:从技术上讲,这不是一个“并发”的NSOperation ,因为-[MyOperation isConcurrent]将会返回NO ,但是它将在后台线程上运行, isConcurrent方法实际上应该被命名为-willCreateOwnThread ,因为这是对方法意图的更准确的描述。)

我知道这是一个老问题,但最近我一直在调查,遇到同样的例子,也有同样的疑问。

如果你所有的工作都可以在main方法中同步运行,那么你不需要并发操作,也不需要重载启动,只需要完成你的工作,并在完成时从main返回。

但是,如果你的工作负载本质上是asynchronous的,即加载一个NSURLConnection,你必须inheritancestart。 当你的启动方法返回时,操作还没有完成。 只有当您手动将KVO通知发送到isFinished和isExecuting标志(例如,asynchronousURL加载完成或失败)时,它才会被NSOperationQueue完成。

最后,当想要启动的asynchronous工作负载需要在主线程上运行循环侦听时,可能需要将启动分派到主线程。 由于工作本身是asynchronous的,它不会限制并发性,但是在工作线程中开始工作可能没有准备好适当的runloop。

@BJHomer的出色答案值得更新。

并发操作应该覆盖start方法而不是main方法。

正如苹果文档所述 :

如果您正在创build并发操作,则至less需要覆盖以下方法和属性:

  • start
  • asynchronous
  • executing
  • finished

正确的实现也需要覆盖cancel 。 使子类线程安全并获得所需的语义正确也是相当棘手的。

因此,我已经把一个完整的工作子类作为Swift在Code Review中实现的一个提议 。 欢迎提出意见和build议。

这个类可以很容易地用作自定义操作类的基类。

看看ASIHTTPRequest 。 它是一个构build在NSOperation作为子类的HTTP包装类,似乎实现了这些。 请注意,截至2011年年中,开发人员build议不要将ASI用于新项目。

关于在NSOperation子类中定义“ 取消 ”属性(或定义“ 取消 ”iVAR),通常不是必需的。 只是因为当USER触发取消时,自定义代码应该总是通知KVO观察者你的操作现在已经完成了。 换句话说, isCancelled => isFinished。

特别是,当NSOperation对象依赖于其他操作对象的完成时,它监视这些对象的isFinished关键path。 由于未能生成完成通知( 在发生取消的情况下 )可以阻止应用程序中执行其他操作。


顺便说一句,@BJ荷马的答案:“isConcurrent方法真的应该被命名为-willCreateOwnThread”使一个很好的感觉

因为如果你不覆盖start-method,只需手动调用NSOperation-Object的默认启动方法,调用线程本身默认是同步的。 所以,NSOperation-Object只是一个非并发操作。

但是,如果你重写start-method,在start-method的实现中,自定义代码应该产生一个单独的线程 …等等,那么你就成功地打破了“调用线程默认同步”的限制,从而使NSOperation-Object成为并发操作,之后可以asynchronous运行。

这篇博文:

http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/

解释为什么你可能需要:

 if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; return; } 

在你的start方法。