如何从 NSOperations 收集数据到数组中?

How can you collect data from NSOperations into an array?

我有一些 NSOperations 可以异步创建一些数据。我想将所有结果收集到一个数组中。因为我在多个不同的线程上访问数组,所以我在数组周围加了锁。

NSOperationQueue 正在将数据附加到数组,但结果似乎遗漏了一些数据对象。每次我 运行 结果似乎都会改变。

我创建了一个简化的示例项目来重现该问题。代码在 Swift 中,但我认为这不是 Swift 特定的。

import UIKit

class ViewController: UIViewController {
    let queue = NSOperationQueue()
    var bucket = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        queue.addObserver(self, forKeyPath: "operations", options: NSKeyValueObservingOptions.New, context: nil)

        for _ in 0..<10 {
            queue.addOperation(NSBlockOperation {
                // Let's pretend that creating the "fish" string is actually potentially
                // expensive and that's why we're doing it in an NSOperation.
                let fish = "fish"

                objc_sync_enter(self.bucket)
                self.bucket.append(fish)

                let fishCount = self.bucket.count
                print("Bucket contains \(fishCount) fish" + ((fishCount != 1) ? "es" : ""))
                objc_sync_exit(self.bucket)
            })
        }
    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if let keyPath = keyPath {
            if let object = object as? NSOperationQueue {
                if object == queue && keyPath == "operations" {
                    if queue.operationCount == 0 {
                        objc_sync_enter(self.bucket)
                        let fishCount = bucket.count
                        print("Bucket finally contains \(fishCount) fish" + ((fishCount != 1) ? "es" : ""))
                        objc_sync_exit(self.bucket)
                    }
                } else {
                    super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
                }
            }
        }
    }
}

结果各不相同,但通常是这样的:

Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 2 fishes
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 3 fishes

此外,有时代码会因 EXC_BAD_ACCESSself.bucket.append(fish)

而崩溃

此外,observeValueForKeyPath 中的行 print("Bucket finally contains \(fishCount) fish" + ((fishCount != 1) ? "es" : "")) 永远不会被调用。我不确定这是否是一个单独的问题。

你应该看看 subclassing NSOperation,因为它是一个抽象 class。 请参阅此 Whosebug question 以了解 subclassing。 考虑到这一点,我建议您在每个操作实例上都有一个标识符 属性 ,以便您可以跟踪您的操作,这样您就可以知道您的所有操作何时完成。您也可以考虑将此代码从视图控制器 class 中拉出并创建一个 class 来处理您的鱼 另外,当您不再对鱼感兴趣但它会帮助您进一步封装猫 :)

The Concurrency Programming Guide 非常善于解释异步应用程序设计的基础知识。

The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task. Because it is abstract, you do not use this class directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or NSBlockOperation) to perform the actual task. Despite being abstract, the base implementation of NSOperation does include significant logic to coordinate the safe execution of your task. The presence of this built-in logic allows you to focus on the actual implementation of your task, rather than on the glue code needed to ensure it works correctly with other system objects.