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

时间:2010-10-04 22:29:54

标签: 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 **");
    }
}

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

另外,当我注释掉那一行时,我会同时运行我的操作。但是,即使我已调用isCancelled

,也不会修改cancelAllOperations标志

6 个答案:

答案 0 :(得分:109)

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

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

  2. 当您在包含此操作的_isCancelled上调用cancelAllOperations时,为什么NSOperationQueue标记未被修改?

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

    1。同时运行NSOperations

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

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

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

    代码中的performSelectorOnMainThread:会导致MyOperation的每个实例始终在主线程上执行其任务。由于一次只能在一个线程上运行一段代码,这意味着没有其他MyOperation可以运行。 NSOperationNSOperationQueue的整个目的是在后台执行某些操作。

    您想要强制进入主线程的唯一时间是您更新用户界面时。如果您需要在MyOperation完成后更新用户界面, 就应该使用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]

    3. 你应该做什么

      标题:

      // 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,如上所述。但是,它会< / 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关键路径。未能生成完成通知(,如果发生取消),可能会阻止在您的应用程序中执行其他操作。


BTW,@ BJ Homer的回答:&#34; isConcurrent方法确实应该命名为-willCreateOwnThread&#34;很有意义

因为如果你不覆盖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方法中。