【问题标题】:Asynchronous methods in NSOperationNSOperation 中的异步方法
【发布时间】:2010-12-08 10:40:24
【问题描述】:

我正在从 Facebook Connect 获取一些数据(使用 FBConnect Objective-C 2.0 框架),并且我在 NSOperation 中完成所有这些工作。它在 NSOperation 中,因为我还有其他几个操作也在运行,这就是其中之一。

问题在于所有的 FBConnect 调用都是异步的。正因为如此,NSOperation 的 main 方法很快结束,操作被标记为完成。

有没有办法克服这个问题? FBConnect 中似乎没有同步选项!

非常感谢,

迈克

【问题讨论】:

  • 如果FBConnect 本质上是异步的,那么真的有必要使用NSOperation 吗?
  • 嗯,是的,因为它是许多排队的任务之一,并且在下载数据后会发生很多处理。
  • 为什么不在异步下载完成后为该处理创建NSOperation

标签: iphone asynchronous nsoperation fbconnect


【解决方案1】:

下面是一个完整的例子。在您的子类中,在您的异步方法完成后,调用 [self completeOperation] 以转换到完成状态。

@interface AsynchronousOperation()
// 'executing' and 'finished' exist in NSOperation, but are readonly
@property (atomic, assign) BOOL _executing;
@property (atomic, assign) BOOL _finished;
@end

@implementation AsynchronousOperation

- (void) start;
{
    if ([self isCancelled])
    {
        // Move the operation to the finished state if it is canceled.
        [self willChangeValueForKey:@"isFinished"];
        self._finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    // If the operation is not canceled, begin executing the task.
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    self._executing = YES;
    [self didChangeValueForKey:@"isExecuting"];

}

- (void) main;
{
    if ([self isCancelled]) {
        return;
    }

}

- (BOOL) isAsynchronous;
{
    return YES;
}

- (BOOL)isExecuting {
    return self._executing;
}

- (BOOL)isFinished {
    return self._finished;
}

- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    self._executing = NO;
    self._finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

@end

【讨论】:

  • 这就是说...对于其他用途,例如子类化 NSObject 并为您处理 NSOperationQueue 的类...确保您实现:-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  • 啊。不就是不。不要定义以 _ 开头的属性。重新定义现有属性。如果需要,请致电super
  • executingfinished 存在于 NSOperation 中,但它们是只读的,因此无法修改。这怎么可能更干净?
  • @JasonMoore 你如何重新定义它们?你只是这样做。这没有复杂性,也没有“不能”。
【解决方案2】:

将您的 FBConnect 调用放入“start”而不是“main”,并管理“isFinished”“isExecuting”属性。 (并为“isConcurrent”返回YES

有关更多详细信息,请参阅 Apple 关于编写 concurrent NSOperations 的文档。

【讨论】:

  • 从 iOS 7.0 开始,应使用 isAsynchronous 而不是 isConcurrent
【解决方案3】:

如果没有别的,请理解这一点:NSOperation 的行为没有什么神奇之处。 NSOperationQueue 只是使用 Key Value Observation 来监控操作。这并不容易的唯一原因是使用的键与 Objective-C 2.0 约定所说的不一样,因此标准的合成设置器不起作用。

结果是当你定义你的NSOperation子类时,你需要提供asynchronousexecutingfinished。最后两个需要您的帮助才能正常工作。

听起来很复杂?不是,只是详细信息。一路上的每一步都很简单且有意义,但在你把它们都做对之前它实际上是行不通的。

首先是表头:

//
//  MyOperation.h

#import <Foundation/Foundation.h>

@interface MyOperation : NSOperation

@property(readonly, getter=isAsynchronous) BOOL asynchronous;
@property(readonly, getter=isExecuting) BOOL executing;
@property(readonly, getter=isFinished) BOOL finished;

@end

当然,您可以在此处将executingfinished 定义为readwrite,因此您无需在实现中将它们重新定义为readwrite。但我想知道只有我的操作才能改变它们的状态。

现在实施。这里有几个步骤:

  • finishedexecuting 属性重新定义为读/写。
  • 完全提供executingfinished 的实现,手动提供正确的KVO 消息传递(所以isExecutingsetExecuting:isFinishedsetFinished:)。
  • 使用@synthesizeexecutingfinished ivars 提供存储空间。
  • 提供asynchronous的实现

(请注意,此代码可能会滚动一点。)

//
//  MyOperation.m

#import "MyOperation.h"

@interface MyOperation()
@property(readwrite) BOOL executing;
@property(readwrite) BOOL finished;
@end

@implementation MyOperation

// Provide your own start.

- (void)start {
    if (self.cancelled) {
        self.finished = YES;
        return;
    }
    NSLog(@"Starting %@", self);
    self.executing = YES;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSLog(@"Finished %@", self);
        self.executing = NO;
        self.finished = YES;
    });
}

// The rest of this is boilerplate.

- (BOOL)isAsynchronous {
    return YES;
}

@synthesize executing = _executing;

- (BOOL)isExecuting {
    @synchronized(self) {
        return _executing;
    }
}

- (void)setExecuting:(BOOL)executing {
    @synchronized(self) {
        if (executing != _executing) {
            [self willChangeValueForKey:@"isExecuting"];
            _executing = executing;
            [self didChangeValueForKey:@"isExecuting"];
        }
    }
}

@synthesize finished = _finished;

- (BOOL)isFinished {
    @synchronized(self) {
        return _finished;
    }
}

- (void)setFinished:(BOOL)finished {
    @synchronized(self) {
        if (finished != _finished) {
            [self willChangeValueForKey:@"isFinished"];
            _finished = finished;
            [self didChangeValueForKey:@"isFinished"];
        }
    }
}


@end

实际上没有必要在 setter 中检查(例如)executing != _executing。通过调用willChangeValueForKey 自动提供正确的行为,盲目地更改值,然后调用didChangeValueForKey。但是条件意味着您可以在赋值上设置断点,并且仅在值更改时停止,而且我发现这对于在实践中调试我的操作非常有用。

我还看到通过在executingfinished 属性之上提供自定义状态来实现这一点。当然,这非常好,并且在某些方面更好……但它也需要比这个示例更多的 KVO 知识,这已经足够了。

最后,请注意,一旦操作开始,我没有添加对取消的支持。为此,您必须覆盖cancel(或者更准确地说,观察isCancelled 的值)并处理它。这会使我的简单 start 示例复杂很多。

我在命令行控制台应用程序中运行此代码,方法是将 15 个操作添加到 maxConcurrentOperationCount 为 5 的队列中,然后使用 waitUntilAllOperationsAreFinished 在队列上等待完成(这就是我为 @987654355 使用后台队列的原因@ 在我的start)。这是输出:

2019-01-22 13:29:32.897893-0800 test[86762:4812871] Starting <MyOperation: 0x10058d2d0>
2019-01-22 13:29:32.897893-0800 test[86762:4812872] Starting <MyOperation: 0x10058d710>
2019-01-22 13:29:32.897903-0800 test[86762:4812873] Starting <MyOperation: 0x100589930>
2019-01-22 13:29:32.898161-0800 test[86762:4812871] Starting <MyOperation: 0x10058edc0>
2019-01-22 13:29:32.898166-0800 test[86762:4812873] Starting <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898487-0800 test[86762:4812872] Finished <MyOperation: 0x100589930>
2019-01-22 13:29:37.898489-0800 test[86762:4812870] Finished <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898548-0800 test[86762:4812874] Finished <MyOperation: 0x10058edc0>
2019-01-22 13:29:37.898797-0800 test[86762:4812870] Starting <MyOperation: 0x100590000>
2019-01-22 13:29:37.899160-0800 test[86762:4812870] Finished <MyOperation: 0x10058d710>
2019-01-22 13:29:37.899651-0800 test[86762:4812870] Starting <MyOperation: 0x1005901a0>
2019-01-22 13:29:37.899933-0800 test[86762:4812874] Starting <MyOperation: 0x100590340>
2019-01-22 13:29:37.900133-0800 test[86762:4812871] Finished <MyOperation: 0x10058d2d0>
2019-01-22 13:29:37.900504-0800 test[86762:4812871] Starting <MyOperation: 0x100590680>
2019-01-22 13:29:37.900583-0800 test[86762:4812874] Starting <MyOperation: 0x1005904e0>
2019-01-22 13:29:42.899325-0800 test[86762:4812871] Finished <MyOperation: 0x100590000>
2019-01-22 13:29:42.899541-0800 test[86762:4812874] Starting <MyOperation: 0x100590820>
2019-01-22 13:29:43.393291-0800 test[86762:4812871] Finished <MyOperation: 0x1005901a0>
2019-01-22 13:29:43.393298-0800 test[86762:4812874] Finished <MyOperation: 0x100590340>
2019-01-22 13:29:43.394531-0800 test[86762:4812874] Finished <MyOperation: 0x1005904e0>
2019-01-22 13:29:43.395380-0800 test[86762:4812874] Finished <MyOperation: 0x100590680>
2019-01-22 13:29:43.396359-0800 test[86762:4812874] Starting <MyOperation: 0x1005909c0>
2019-01-22 13:29:43.397440-0800 test[86762:4812872] Starting <MyOperation: 0x100590b60>
2019-01-22 13:29:43.397891-0800 test[86762:4812874] Starting <MyOperation: 0x100590d00>
2019-01-22 13:29:43.399711-0800 test[86762:4812872] Starting <MyOperation: 0x100590ea0>
2019-01-22 13:29:47.900058-0800 test[86762:4812984] Finished <MyOperation: 0x100590820>
2019-01-22 13:29:48.892953-0800 test[86762:4812872] Finished <MyOperation: 0x100590d00>
2019-01-22 13:29:48.892970-0800 test[86762:4812871] Finished <MyOperation: 0x100590b60>
2019-01-22 13:29:48.893019-0800 test[86762:4813163] Finished <MyOperation: 0x100590ea0>
2019-01-22 13:29:48.893562-0800 test[86762:4812984] Finished <MyOperation: 0x1005909c0>
Program ended with exit code: 0

【讨论】:

    【解决方案4】:

    这个怎么样?

    //
    //  Operation.m
    
    #import "Operation.h"
    
    @interface Operation() 
    
    @property (nonatomic, strong) dispatch_semaphore_t semaphore;
    
    @end
    
    @implementation Operation 
    
    - (void)main {
        [self doWorkWithCompletion:^{
            dispatch_semaphore_signal(self.semaphore);
        }];    
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    }
    
    ....
    
    @end
    

    【讨论】:

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