Alamofire请求不在NSOperation内运行完成块

时间:2016-10-14 21:15:59

标签: ios swift concurrency alamofire nsoperation

Note:虽然在SO中还有其他类似的问题,但在这些问题中,作者似乎都没有自己控制Operation的生命周期。请在提及另一个问题之前仔细阅读。

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

首先,我在操作中使用main()方法来执行手头的任务并且运行良好。现在我需要运行几个单独的任务来检索有关我在此步骤中获得的每个/每个设备的信息。为此,在尝试获取其他信息之前,我需要确保设备实际上在Core Data中。出于这个原因,我想确保在解决相关请求之前,我决定任务何时完成 - 这是所有设备在缓存中是安全的还是声音的。

问题在于,即使我检查过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()执行完成处理程序。

1 个答案:

答案 0 :(得分:1)

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

您提到您正在呼叫waitUntilAllOperationsAreFinished。虽然这是一个令人陶醉的简单解决方案,等待一系列操作完成,但你绝不应该从主线程中做到这一点。永远不应该阻止主线程(或者,至少不超过几毫秒)。它可能导致不合标准的用户体验(应用程序冻结),并且您的应用程序很容易被“看门狗”进程立即终止。我怀疑Alamofire作者在默认情况下将完成处理程序调度到主队列时感觉非常舒服的原因之一是它不仅经常有用且方便,而且他们知道永远不会阻塞主线程。 / p>

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

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,技术上你可以这样做,但是你应该只在你将“等待”发送到某个后台队列(例如全局队列,但显然不是你自己的操作)时才这样做您添加所有这些操作的队列)。我认为这是一种浪费的模式(当你有一个非常好的机制来指定一个完成操作时,为什么要占用一些全局工作线程等待操作完成)。