【问题标题】:Subclassing NSOperation to be concurrent and cancellable将 NSOperation 子类化为并发和可取消
【发布时间】:2011-04-21 01:15:26
【问题描述】:

我找不到关于如何将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

【问题讨论】:

    标签: iphone nsoperation nsoperationqueue performselector


    【解决方案1】:

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

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

    Apple Documentation中所述:

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

    • start
    • asynchronous
    • executing
    • finished

    正确的实现也需要覆盖cancel。制作子类线程安全 并正确获取所需的语义也非常棘手。

    因此,我将一个完整且有效的子类作为proposal implemented in Swift 放入代码审查中。欢迎提出意见和建议。

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

    【讨论】:

    • 我看到了很多例子,但只有你的人关注线程安全以获得 isExecuting 和 isFinished 的值。??
    【解决方案2】:

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

    特别是,当 NSOperation 对象依赖于其他操作对象的完成时,它会监视那些对象的 isFinished 键路径。因此,未能生成完成通知(如果发生取消)可能会阻止在您的应用程序中执行其他操作。


    顺便说一句,@BJ Homer 的回答:“isConcurrent 方法确实应该命名为 -willCreateOwnThread”,这很有意义

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

    但是,如果您确实覆盖了 start-method,在 start-method 实现中,自定义代码应该生成一个单独的线程...等,那么您成功打破了“调用线程默认为同步”,因此使 NSOperation-Object 成为并发操作,之后可以异步运行。

    【讨论】:

      【解决方案3】:

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

      如果你的所有工作都可以在 main 方法中同步运行,那么你不需要并发操作,也不需要覆盖 start,只需做你的工作并在完成后从 main 返回。

      但是,如果您的工作负载本质上是异步的 - 即加载 NSURLConnection,您必须子类 start。当您的 start 方法返回时,操作尚未完成。只有当您手动将 KVO 通知发送到 isFinished 和 isExecuting 标志时(例如,一旦异步 URL 加载完成或失败),NSOperationQueue 才会认为它已完成。

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

          1. 您是否需要出现在代码中 cmets 中的 performSelectorOnMainThread: 段?这段代码有什么作用?

          2. 为什么在包含该操作的NSOperationQueue上调用cancelAllOperations_isCancelled标志没有被修改?

          让我们按顺序处理这些。我将假设您的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
          

          以及实现:

          // 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,因为这样更准确地描述了该方法的意图。)

          【讨论】:

          • +1 但是请注意,通常不需要子类化 NSOperation,因为您可以从 NSInvocationOperation 子类中获得相当多的功能。
          • 非常感谢。很好的解释。 @Dave DeLong,如果要取消它,则需要子类化 NSOperation,因为 NSInvocationOperation 不提供取消方法,不是吗?
          • 我同意所有这些,除了您从操作中触发 UI 更改的部分。操作不应该直接链接到 UI,这更多的是模型工作,而不是视图工作。
          • @Ricardo 你不应该覆盖-cancel,因为这会发生在调用线程上。相反,您应该定期检查您的操作逻辑中的[self isCancelled],并在设置时适当地退出。
          • 感谢 BJ 荷马。阅读此技术说明后:developer.apple.com/library/ios/#technotes/tn2109/_index.html 我看你是对的。但是,我应该如何取消长请求?谢谢。
          【解决方案6】:

          这篇博文:

          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 方法中。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-10-30
            • 1970-01-01
            • 2012-09-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多