子类化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
。
好,据我了解,你有两个问题:
-
你需要代码中的注释中出现的
performSelectorOnMainThread:
段吗? 这个代码是做什么的? -
为什么在调用包含此操作的
NSOperationQueue
上的cancelAllOperations
时,_isCancelled
标志未被修改?
让我们按顺序处理。 我将假设你的NSOperation
的子类被称为MyOperation
,只是为了便于解释。 我会解释你的误解,然后给出一个更正的例子。
1.同时运行NSOperations
大多数时候,你会用NSOperation
来使用NSOperationQueue
,在你的代码中,这听起来就是你正在做的事情。 在这种情况下, MyOperation
将始终在后台线程上运行,而不pipe-(BOOL)isConcurrent
方法返回的是什么,因为NSOperationQueue
被明确devise为在后台运行操作。
因此,您通常不需要重写-[NSOperation start]
方法,因为默认情况下它只是调用-main
方法。 这是你应该重写的方法。 默认的启动方法已经在适当的时候处理了isExecuting
和isFinished
。
所以,如果你想NSOperation
在后台运行,只需重写NSOperationQueue
方法并将其放在NSOperationQueue
。
代码中的performSelectorOnMainThread:
会导致MyOperation
每个实例始终在主线程上执行其任务。 由于一次只能在一个线程上运行一段代码,这意味着没有其他MyOperation
可以运行。 NSOperation
和NSOperationQueue
的全部目的是在后台做一些事情。
唯一一次你想强制主线程的东西是当你更新用户界面。 如果您需要在MyOperation
完成时更新UI,那么应该使用performSelectorOnMainThread:
我将在下面的例子中演示如何做到这一点。
2.取消NSOperation
-[NSOperationQueue cancelAllOperations]
调用-[NSOperation cancel]
方法,导致后续调用-[NSOperation isCancelled]
返回YES
。 但是 ,你已经做了两件事情,使这个无效。
-
您正在使用
@synthesize isCancelled
来覆盖NSOperation的-isCancelled
方法。 没有理由这样做。NSOperation
已经以完全可以接受的方式实现了。 -
您正在检查自己的
_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
请注意,您不需要对isExecuting
, isCancelled
或isFinished
执行任何操作。 这些都是为你自动处理的。 只需重写-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
方法。