我无法找到关于如何将NSOperation
子类化为并发以及支持取消的良好文档。我阅读了Apple文档,但我无法找到“官方”示例。
这是我的源代码:
@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
标志
答案 0 :(得分:109)
好的,据我所知,你有两个问题:
您是否需要代码中的评论中显示的performSelectorOnMainThread:
细分?那段代码做了什么?
当您在包含此操作的_isCancelled
上调用cancelAllOperations
时,为什么NSOperationQueue
标记未被修改?
让我们按顺序处理这些问题。我将假设你的NSOperation
的子类被称为MyOperation
,只是为了便于解释。我将解释你的误解,然后给出一个更正的例子。
大多数情况下,您会将NSOperation
与NSOperationQueue
一起使用,并且从您的代码中,听起来就像您正在做的那样。在这种情况下,无论MyOperation
方法返回什么,您的-(BOOL)isConcurrent
将始终在后台线程上运行,因为NSOperationQueue
明确设计为在后台运行操作。
因此,您通常不需要覆盖-[NSOperation start]
方法,因为默认情况下它只调用-main
方法。这是你应该重写的方法。默认-start
方法已在适当的时间为您处理设置isExecuting
和isFinished
。
因此,如果您希望在后台运行NSOperation
,只需覆盖-main
方法并将其放在NSOperationQueue
上。
代码中的performSelectorOnMainThread:
会导致MyOperation
的每个实例始终在主线程上执行其任务。由于一次只能在一个线程上运行一段代码,这意味着没有其他MyOperation
可以运行。 NSOperation
和NSOperationQueue
的整个目的是在后台执行某些操作。
您想要强制进入主线程的唯一时间是您更新用户界面时。如果您需要在MyOperation
完成后更新用户界面, 就应该使用performSelectorOnMainThread:
。我将在下面的例子中展示如何做到这一点。
-[NSOperationQueue cancelAllOperations]
调用-[NSOperation cancel]
方法,这会导致后续调用-[NSOperation isCancelled]
返回YES
。 然而,你做了两件事使它无效。
您正在使用@synthesize isCancelled
覆盖NSOperation的-isCancelled
方法。没有理由这样做。 NSOperation
已经以完全可以接受的方式实施-isCancelled
。
您正在检查自己的_isCancelled
实例变量,以确定操作是否已被取消。 NSOperation
保证[self isCancelled]
如果操作已取消,将返回YES
。 不确保您的自定义setter方法将被调用,并且您自己的实例变量是最新的。您应该检查[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
,如上所述。但是,它会< / em>在后台线程上运行。isConcurrent
方法确实应该命名为-willCreateOwnThread
,因为这是对方法意图的更准确的描述。)
答案 1 :(得分:3)
@BJHomer的优秀答案值得更新。
并发操作应覆盖:\/$
方法而不是start
。
如Apple Documentation中所述:
如果要创建并发操作,则至少需要覆盖以下方法和属性:
main
start
asynchronous
executing
正确的实现也要求覆盖finished
。使子类线程安全并获得正确的语义也是非常棘手的。
因此,我在Code Review中将一个完整且有效的子类作为proposal implemented in Swift。欢迎提出意见和建议。
此类可以很容易地用作自定义操作类的基类。
答案 2 :(得分:2)
我知道这是一个老问题,但我最近一直在调查这个问题并遇到相同的例子,并有同样的疑问。
如果您的所有工作都可以在main方法中同步运行,那么您不需要并发操作,也不需要覆盖启动,只需完成工作并在完成后从main返回。
但是,如果您的工作负载本质上是异步的 - 即加载NSURLConnection,则必须创建子类。当您的start方法返回时,操作尚未完成。当您手动向isFinished和isExecuting标志发送KVO通知时(例如,异步URL加载完成或失败后),只会被NSOperationQueue视为已完成。
最后,当您想要启动的异步工作负载需要在主线程上进行运行循环侦听时,可能需要将启动分配给主线程。由于工作本身是异步的,它不会限制你的并发性,但是在工作线程中开始工作可能没有准备好正确的runloop。
答案 3 :(得分:0)
看看ASIHTTPRequest。它是一个构建在NSOperation
之上的HTTP包装类作为子类,似乎实现了这些。请注意,截至2011年中期,开发人员建议不要将ASI用于新项目。
答案 4 :(得分:0)
关于定义&#34; 已取消&#34;在NSOperation子类中的属性(或定义&#34; _cancelled &#34; iVAR),通常不是必需的。只是因为当USER触发取消时,自定义代码应始终通知KVO观察者您的操作现在已完成及其工作。换句话说, isCancelled =&gt; isFinished 强>
特别是,当NSOperation对象依赖于其他操作对象的完成时,它会监视这些对象的isFinished关键路径。未能生成完成通知(,如果发生取消),可能会阻止在您的应用程序中执行其他操作。
因为如果你不覆盖start-method,只需手动调用NSOperation-Object的default-start-method,默认情况下,调用线程本身是同步的;所以,NSOperation-Object只是一个非并发操作。
但是,如果你重写start-method,在start-method实现中,自定义代码应该生成一个单独的线程 ...等,那么你成功地打破了#34;调用的限制 - 线程默认为同步&#34;,因此使NSOperation-Object成为并发操作,之后可以异步运行。
答案 5 :(得分:-2)
此博文:
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
方法中。