将 NSOperation 子类化为并发且可取消
-
27-09-2019 - |
题
我无法找到有关如何子类化的良好文档 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 **");
}
}
在我发现的示例中,我不明白为什么执行SelectorOnMainThread:用来。它会阻止我的操作同时运行。
另外,当我注释掉该行时,我的操作会同时运行。但是,那 isCancelled
标志没有被修改,即使我已经打电话了 cancelAllOperations
.
解决方案
好吧,据我了解,你有两个问题:
您需要吗
performSelectorOnMainThread:
出现在代码注释中的段?该代码有什么作用?为什么是
_isCancelled
调用时标志不会被修改cancelAllOperations
在NSOperationQueue
包含这个操作?
让我们按顺序处理这些。我假设你的子类 NSOperation
叫做 MyOperation
, ,只是为了便于解释。我会解释你的误解,然后给出一个正确的例子。
1.同时运行 NSOperations
大多数时候,你会使用 NSOperation
与一个 NSOperationQueue
, ,从你的代码来看,这听起来像是你正在做的事情。在这种情况下,你的 MyOperation
将始终在后台线程上运行,无论什么 -(BOOL)isConcurrent
方法返回,因为 NSOperationQueue
被明确设计为在后台运行操作。
因此,您通常不需要覆盖 -[NSOperation start]
方法,因为默认情况下它只是调用 -main
方法。这是您应该重写的方法。默认 -start
方法已经处理设置 isExecuting
和 isFinished
在适当的时间为您服务。
所以如果你想要一个 NSOperation
要在后台运行,只需覆盖 -main
方法并将其放在 NSOperationQueue
.
这 performSelectorOnMainThread:
在你的代码中会导致每个实例 MyOperation
始终在主线程上执行其任务。由于一个线程上一次只能运行一段代码,这意味着没有其他代码 MyOperation
s 可能正在运行。整个目的是 NSOperation
和 NSOperationQueue
就是在后台做一些事情。
唯一一次你想要将事情强制到主线程上是当你更新用户界面时。如果您需要更新 UI MyOperation
完成, 那 是你应该使用的时候 performSelectorOnMainThread:
. 。我将在下面的示例中展示如何做到这一点。
2.取消 NSOperation
-[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
正如上面所实现的。然而,它 将要 在后台线程上运行。这 isConcurrent
方法确实应该命名 -willCreateOwnThread
, ,因为这是对该方法意图的更准确描述。)
其他提示
@BJHomer 的出色答案值得更新。
并发操作应该覆盖 start
方法而不是 main
.
如中所述 苹果文档:
如果要创建并发操作,则至少需要重写以下方法和属性:
start
asynchronous
executing
finished
适当的实施也 需要 覆盖 cancel
以及。制作子类 线程安全 并且获得正确的所需语义也相当棘手。
因此,我将一个完整且可工作的子类作为 用 Swift 实现的提案 在代码审查中。欢迎提出意见和建议。
该类可以轻松用作自定义操作类的基类。
我知道这是一个老问题,但我最近一直研究这一点,并遇到了同样的例子,有同样的疑惑。
如果您的所有工作可以同步的主要方法内部运行,你不需要并发操作,无论是压倒一切的开始,只是做与主您的工作和回报完成时。
不过,如果您的工作负载在本质上是异步的 - 即加载NSURLConnection的,你必须继承开始。当您启动方法返回,操作尚未完成。它只会被认为是本NSOperationQueue完成后您手动发送志愿通知给isFinished和isExecuting标志(例如,一旦异步网址载入完成或失败)。
最后,人们可能会在异步工作负载要启动需要在主线程上运行循环收听希望调度开始到主线程。由于工作本身是异步的,它不会限制你的并发性,但在开始工作线程的工作可能不会有一个适当的runloop准备好了。
看一看 ASIHTTPRequest 。它是包装建立在NSOperation
作为一个子类的顶级的HTTP,似乎实现这些。需要注意的是由于2011年中期,开发商建议不要使用ASI新项目。
关于界定 “的取消强>” 属性(或限定 “的 _cancelled 强>” 的Ivar)的NSOperation子类的内部,通常是没有必要的。很简单,因为当用户触发取消,自定义代码应该总是通知志愿观察家你的操作是现在的完成其工作。换句话说,的 isCancelled => isFinished。强>
特别地,当对象的NSOperation依赖于其他动作的对象的完成,它监视这些对象的isFinished关键路径。未能产生一个结束通知(的在取消的情况下发生强>)因此,能够防止其它操作的执行在应用程序中。
BTW,@BJ荷马的回答道: “isConcurrent方法真的应该是命名-willCreateOwnThread” 使得很多意义!
因为如果你不重写启动方法,只需手动调用的NSOperation-对象的默认的启动方法,调用线程本身是,默认情况下,同步;所以,的NSOperation - 对象仅仅是一个非并发操作。
但是,如果确实要替换的启动方法,内部启动的方法实现,自定义代码的应该产生一个独立的线程 ...等,那么你就成功破发“调用线程默认存在的限制同步”,因此使的NSOperation - 对象成为一个并行操作,它可以以后异步运行。
此博客文章:
http://www.dribin.org/dave /博客/存档/ 2009/09/13 / snowy_concurrent_operations /
解释了为什么您可能需要:
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
在start
方法。