Iphone 将NSO操作子类化为并发且可取消

Iphone 将NSO操作子类化为并发且可取消,iphone,nsoperation,nsoperationqueue,performselector,Iphone,Nsoperation,Nsoperationqueue,Performselector,我找不到关于如何将NSOperation子类化为并发并支持取消的好文档。我读了苹果的文档,但找不到“官方”的例子 以下是我的源代码: @synthesize isExecuting = _isExecuting; @synthesize isFinished = _isFinished; @synthesize isCancelled = _isCancelled; - (BOOL)isConcurrent { return YES; } - (void)start { /* WHY

我找不到关于如何将
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 **");
    }
}
在我找到的示例中,我不明白为什么要使用performSelectorOnMainThread:。这将阻止我的操作同时运行


另外,当我注释掉那一行时,我的操作会同时运行。但是,
isCancelled
标志没有被修改,即使我已经调用了
cancelAllOperations

查看。它是一个HTTP包装类,构建在
NSOperation
的子类之上,似乎实现了这些功能。请注意,从2011年年中开始,开发人员建议不要在新项目中使用ASI。

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

  • 是否需要代码注释中显示的
    performSelectorOnMainThread:
    段?那代码是做什么的

  • 在包含此操作的
    NSOperationQueue
    上调用
    cancelAllOperations
    时,为什么没有修改
    \u isCancelled
    标志

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

    1.同时运行NSO操作 大多数情况下,您会将
    NSOperation
    s与
    NSOperationQueue
    一起使用,从您的代码中,听起来您就是在这样做的。在这种情况下,您的
    MyOperation
    将始终在后台线程上运行,而不管
    -(BOOL)concurrent
    方法返回什么,因为
    NSOperationQueue
    被明确设计为在后台运行操作

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

    因此,如果希望
    NSOperation
    在后台运行,只需重写
    -main
    方法并将其置于
    NSOperationQueue
    上即可

    代码中的
    performSelectorOnMainThread:
    将导致
    MyOperation
    的每个实例始终在主线程上执行其任务。由于一次只能在线程上运行一段代码,这意味着没有其他
    MyOperation
    s可以运行。
    NSOperation
    NSOperationQueue
    的全部目的是在后台做一些事情

    只有在更新用户界面时,才需要将内容强制到主线程上。如果在
    MyOperation
    完成时需要更新UI,则应使用
    performSelectorOnMainThread:
    。我将在下面的示例中演示如何做到这一点

    2.取消操作
    -[NSOperationQueue cancelAllOperations]
    调用
    -[NSOperation cancel]
    方法,这会导致对
    -[NSOperation isCancelled]
    的后续调用返回
    然而,您已经做了两件事使其无效

  • 您正在使用
    @isCancelled
    覆盖NSOperation的
    -isCancelled
    方法。没有理由这样做<代码>NSOperation已以完全可接受的方式实现了
    -isCancelled

  • 您正在检查自己的
    \u isCancelled
    实例变量,以确定操作是否已取消
    NSOperation
    保证如果操作已取消,
    [self isCancelled]
    将返回
    YES
    。它不能保证调用自定义setter方法,也不能保证自己的实例变量是最新的。您应该检查
    [自我取消]

  • 你应该做什么 标题:

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

    此博文:

    解释为什么您可能需要:

    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }
    

    在你的
    start
    方法中。

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

    如果您的所有工作都可以在main方法内同步运行,那么您不需要并发操作,也不需要重写start,只需完成工作并在完成后从main返回即可

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

    最后,当要启动的异步工作负载需要在主线程上侦听运行循环时,可能需要将start分派到主线程。正如作品本身一样