Alamofire 请求不是 运行 NSOperation 中的完成块

Alamofire request not running completion block inside NSOperation

Note:虽然在 SO 中还有其他与此类似的问题,但在其中 none 中,作者似乎自己控制了 Operation 的生命周期。请在参考另一个问题之前通读。

我在 Swift 3.0 中创建了一个 [NS]Operation 来下载、解析和缓存 Core Data 中的一些数据。

起初,我在操作中使用main()方法来执行手头的任务,效果很好。现在我需要 运行 几个单独的任务来检索关于我在这一步中获得的 each/every 设备的信息。为此,在尝试获取其他信息之前,我需要确保这些设备实际上位于核心数据中。出于这个原因,我想确保在触发相关请求之前决定任务何时完成——也就是所有设备在缓存中安全无虞的时候。

问题是,即使我已经检查过 Alamofire 确实执行了请求并且服务器确实发送了数据,但永远不会执行标有注释 [THIS WONT EXECUTE!] 的完成块。这会导致队列停止,因为操作在所述完成块内被标记为 finished,这是所需的行为。

有人知道这里会发生什么吗?

class FetchDevices: Operation {

    var container: NSPersistentContainer!
    var alamofireManager: Alamofire.SessionManager!
    var host: String!
    var port: Int!

    private var _executing = false
    private var _finished = false

    override internal(set) var isExecuting: Bool {
        get {
            return _executing
        }

        set {
            willChangeValue(forKey: "isExecuting")
            _executing = newValue
            didChangeValue(forKey: "isExecuting")
        }
    }

    override internal(set) var isFinished: Bool {
        get {
            return _finished
        }

        set {
            willChangeValue(forKey: "isFinished")
            _finished = newValue
            didChangeValue(forKey: "isFinished")
        }
    }

    override var isAsynchronous: Bool {
        return true
    }

    init(usingContainer container: NSPersistentContainer, usingHost host: String, usingPort port: Int) {
        super.init()

        self.container = container
        self.host = host
        self.port = port

        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForResource = 10 // in seconds
        self.alamofireManager = Alamofire.SessionManager(configuration: configuration)
    }

    override func start() {
        if self.isCancelled {
            self.isFinished = true
            return
        }

        self.isExecuting = true

        alamofireManager!.request("http://apiurlfor.devices")
            .validate()
            .responseJSON { response in
                // THIS WONT EXECUTE!
                if self.isCancelled {
                    self.isExecuting = false
                    self.isFinished = true
                    return
                }

                switch response.result {
                case .success(let value):
                    let jsonData = JSON(value)

                    self.container.performBackgroundTask { context in
                        for (_, rawDevice):(String, JSON) in jsonData {
                            let _ = Device(fromJSON: rawDevice, usingContext: context)
                        }

                        do {
                            try context.save()
                        } catch {
                            let saveError = error as NSError
                            print("\(saveError), \(saveError.userInfo)")
                        }

                        self.isExecuting = false
                        self.isFinished = true
                    }

                case .failure(let error):
                    print("May Day! May Day! \(error)")
                    self.isExecuting = false
                    self.isFinished = true
                }
        }
    }
}

一条可能有用的信息是,在我对所有操作进行排队的方法中,我使用 queue.waitUntilAllOperationsAreFinished() 在所有操作完成后执行完成处理程序。

问题是您有其他东西阻塞了主线程,responseJSON 用于关闭主线程,从而导致死锁。您可以确认将 responseJSON 快速替换为 .responseJSON(queue: .global()) 以使 Alamofire 使用主队列以外的队列,您将看到此行为发生变化。但是如果你这样做(仅用于诊断目的),你应该把它改回来,然后把你的注意力转移到识别和消除任何阻塞主线程的东西(即不要在主线程上等待),因为你永远不应该阻塞主线程。


您提到您正在呼叫 waitUntilAllOperationsAreFinished。虽然这是等待一系列操作完成的非常简单的解决方案,但您永远不应该在主线程中这样做。主线程不应该被阻塞(或者,至少,不超过几毫秒)。它可能会导致不合标准的用户体验(应用程序冻结),并且您的应用程序很容易被 "watch dog" 进程立即终止。我怀疑 Alamofire 作者在默认情况下将他们的完成处理程序分派到主队列感到如此舒服的原因之一是它不仅通常有用和方便,而且他们知道永远不会阻塞主线程。

使用操作队列时,避免等待的典型模式是使用完成操作:

let completionOperation = BlockOperation {
    // something that we'll do when all the operations are done
}

for object in arrayOfObjects {
    let networkOperation = ...
    completionOperation.addDependency(networkOperation)
    queue.addOperation(networkOperation)
}

OperationQueue.main.addOperation(completionOperation)

如果您使用调度组和调度组 "notify",您可以实现类似的效果(但是,通常情况下,如果您使用操作队列,为了保持一致性,您通常会留在操作队列范例中).

如果你想调用 waitUntilAllOperationsAreFinished,从技术上讲你可以这样做,但只有当你将 "wait" 分派到某个后台队列(例如全局队列,但显然不是到您自己的操作队列,您在其中添加了所有这些操作)。不过,我认为这是一种浪费模式(当你有一个非常好的指定完成操作的机制时,为什么要占用一些全局工作线程等待操作完成)。