将NSOperation子类化为并发和可取消

thi*_*ryb 50 iphone nsoperation nsoperationqueue performselector

我无法找到关于如何子类化为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 **");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

此外,当我注释掉该行时,我会同时运行我的操作.但是,isCancelled即使我已经调用,也不会修改该标志cancelAllOperations.

BJ *_*mer 110

好的,据我所知,你有两个问题:

  1. 您是否需要performSelectorOnMainThread:代码中的注释中显示的细分?那段代码做了什么?

  2. 为什么_isCancelled当你打电话标志不修改cancelAllOperationsNSOperationQueue包含此操作?

让我们按顺序处理这些问题.我将假设你的子类NSOperation被调用MyOperation,只是为了便于解释.我将解释你的误解,然后给出一个更正的例子.

1.同时运行NSOperations

大多数情况下,你会使用NSOperations NSOperationQueue,而在你的代码中,听起来就像你正在做的那样.在这种情况下,MyOperation无论-(BOOL)isConcurrent方法返回什么,您将始终在后台线程上运行,因为NSOperationQueues明确设计为在后台运行操作.

因此,您通常不需要覆盖该-[NSOperation start]方法,因为默认情况下它只是调用该-main方法.这是你应该重写的方法.默认-start方法已在适当的时间处理设置isExecutingisFinished为您处理.

因此,如果您希望NSOperation在后台运行,只需覆盖该-main方法并将其放在NSOperationQueue.

performSelectorOnMainThread:在您的代码会导致每个实例MyOperation的主线程上总是执行它的任务.由于一次只能在一个线程上运行一段代码,这意味着没有其他MyOperations可以运行.的全部目的NSOperationNSOperationQueue是做事情的背景.

您想要强制进入主线程的唯一时间是您更新用户界面时.如果您需要在MyOperation完成时更新UI ,就是您应该使用的时间performSelectorOnMainThread:.我将在下面的示例中说明如何执行此操作.

2.取消NSOperation

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

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

  2. 您正在检查自己的_isCancelled实例变量以确定操作是否已被取消.NSOperation保证在操作被取消时[self isCancelled]返回YES.它并不能保证您定制的setter方法会被调用,也不是说你自己的实例变量是最新的.你应该检查[self isCancelled]

你应该做什么

标题:

// MyOperation.h
@interface MyOperation : NSOperation {
}
@end
Run Code Online (Sandbox Code Playgroud)

并实施:

// 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
Run Code Online (Sandbox Code Playgroud)

请注意,你不需要用做任何事情isExecuting,isCancelledisFinished.这些都是自动处理的.只需覆盖该-main方法即可.就这么简单.

(A注:从技术上讲,这不是一个"并行" NSOperation,在一定意义-[MyOperation isConcurrent]将返回NO如上实现.然而,它,就会被在后台线程上运行的.isConcurrent方法真的应该被命名-willCreateOwnThread,因为这是一个更准确的描述方法的意图.)

  • 我同意所有这些,除了您从操作触发UI更改的部分.操作不应直接链接到UI,这是更多的模型工作,而不是查看工作. (3认同)
  • 谢谢BJ荷马.阅读本技术说明后:http://developer.apple.com/library/ios/#technotes/tn2109/_index.html我看到你是对的.但是,我应该如何取消长期请求?谢谢. (3认同)
  • 非常感谢.很棒的解释.@Dave DeLong,如果要取消它,则需要继承NSOperation,因为NSInvocationOperation不提供取消方法,不是吗? (2认同)

Cou*_*per 5

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

并发操作应该重写该start方法而不是main.

正如苹果文档中所述:

如果要创建并发操作,则至少需要重写以下方法和属性:

  • start
  • asynchronous
  • executing
  • finished

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

因此,我将一个完整且可工作的子类作为在 Code Review中用 Swift 实现的提案。欢迎提出意见和建议。

该类可以轻松用作自定义操作类的基类。