Alamofire请求在应用程序生命周期中阻止

时间:2017-04-10 15:17:35

标签: ios alamofire nsoperationqueue operation

我使用OperationOperationQueue在Alamofire遇到麻烦。

我有一个名为OperationQueue的{​​{1}}并且我将一些操作(包装AlamofireRequest)推入其中,一切正常,但在应用程序生活期间,所有Alamofire请求都不会被发送。我的队列越来越大,没有请求到最后。

我没有计划随时重现它。

有人有帮助我的线索吗?

以下是代码示例

BackgroundAlamoSession

NetworkingQueue

AbstractOperation.swift

let configuration = URLSessionConfiguration.background(withIdentifier: "[...].background")
self.networkingSessionManager = Alamofire.SessionManager(configuration: configuration)

具体的操作实现

import UIKit
import XCGLogger

class AbstractOperation:Operation {

    private let _LOGGER:XCGLogger = XCGLogger.default

    enum State:String {
        case Ready = "ready"
        case Executing = "executing"
        case Finished = "finished"

        var keyPath: String {
            get{
                return "is" + self.rawValue.capitalized
            }
        }
    }

    override var isAsynchronous:Bool {
        get{
            return true
        }
    }

    var state = State.Ready {
        willSet {
            willChangeValue(forKey: self.state.rawValue)
            willChangeValue(forKey: self.state.keyPath)
            willChangeValue(forKey: newValue.rawValue)
            willChangeValue(forKey: newValue.keyPath)
        }
        didSet {
            didChangeValue(forKey: oldValue.rawValue)
            didChangeValue(forKey: oldValue.keyPath)
            didChangeValue(forKey: self.state.rawValue)
            didChangeValue(forKey: self.state.keyPath)
        }
    }


    override var isExecuting: Bool {
        return state == .Executing
    }

    override var isFinished:Bool {
        return state == .Finished
    }

}

NetworkQueue

import UIKit
import XCGLogger
import SwiftyJSON

class FetchObject: AbstractOperation {

    public let _LOGGER:XCGLogger = XCGLogger.default

    private let _objectId:Int
    private let _force:Bool

    public var object:ObjectModel?


    init(_ objectId:Int, force:Bool) {
        self._objectId = objectId
        self._force = force
    }

    convenience init(_ objectId:Int) {
        self.init(objectId, force:false)
    }

    override var desc:String {
        get{
            return "FetchObject(\(self._objectId))"
        }
    }

    public override func start(){
        self.state = .Executing
    _LOGGER.verbose("Fetch object operation start")

        if !self._force {
            let objectInCache:objectModel? = Application.main.collections.availableObjectModels[self._objectId]

            if let objectInCache = objectInCache {
                _LOGGER.verbose("object with id \(self._objectId) founded on cache")
                self.object = objectInCache
                self._LOGGER.verbose("Fetch object operation end : success")
                self.state = .Finished
                return
            }
        }

        if !self.isCancelled {

            let url = "[...]\(self._objectId)"

            _LOGGER.verbose("Requesting object with id \(self._objectId) on server")

            Application.main.networkingSessionManager.request(url, method : .get)
                .validate()
                .responseJSON(
                    completionHandler: { response in
                        switch response.result {
                        case .success:
                            guard let raw:Any = response.result.value else {
                                self._LOGGER.error("Error while fetching json programm : Empty response")
                                self._LOGGER.verbose("Fetch object operation end : error")
                                self.state = .Finished
                                return
                            }
                            let data:JSON = JSON(raw)
                            self._LOGGER.verbose("Received object from server \(data["bId"])")
                            self.object = ObjectModel(objectId:data["oId"].intValue,data:data)
                            Application.main.collections.availableobjectModels[self.object!.objectId] = self.object
                            self._LOGGER.verbose("Fetch object operation end : success")
                            self.state = .Finished
                            break
                        case .failure(let error):
                            self._LOGGER.error("Error while fetching json program \(error)")
                            self._LOGGER.verbose("Fetch object operation end : error")
                            self.state = .Finished
                            break
                        }
                })
        } else {
            self._LOGGER.verbose("Fetch object operation end : cancel")
            self.state = .Finished
        }
    }
}

我如何在另一个操作中使用它并等待结果

class MyQueue {
    public static let networkQueue:SaootiQueue = SaootiQueue(name:"NetworkQueue", concurent:true)
}

我如何使用KVO

进行主要操作
let getObjectOperation:FetchObject = FetchObject(30)
SaootiQueue.networkQueue.addOperations([getObjectOperation], waitUntilFinished: true)

一些澄清:

我的应用程序是一个广播播放器,我需要在播放音乐和背景时获取当前正在播放的节目。这就是我需要后台会话的原因。

事实上,当应用程序处于前台时,我也会使用后台会话进行所有网络连接。我应该避免吗?

我正在使用的等待来自另一个队列,并且从不在主队列中使用(我知道它是一个线程反模式,我会处理它)。

实际上它在我进行两次网络操作时使用,第二次取决于第二次的结果。我在第一次手术后等待,以避免KVO观察。我应该避免吗?

其他编辑:

当我说“我的队列变得越来越大,没有请求到达目的地”时,这意味着在应用程序生命周期的某个时刻,暂时随机(我无法找到每次都重现它的方法) ),Alamofire请求没有达到响应方法。

因为let getObjectOperation:FetchObject = FetchObject(30) operation.addObserver(self, forKeyPath: #keyPath(Operation.isFinished), options: [.new], context: nil) operation.addObserver(self, forKeyPath: #keyPath(Operation.isCancelled), options: [.new], context: nil) queue.addOperation(operation) //[...] override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if let operation = object as? FetchObject { operation.removeObserver(self, forKeyPath: #keyPath(Operation.isFinished)) operation.removeObserver(self, forKeyPath: #keyPath(Operation.isCancelled)) if keyPath == #keyPath(Operation.isFinished) { //Do something } } 包装器没有结束而队列正在增长。

顺便说一句,我正在努力将Alamofire请求转换为Operation以获取线索,并且我在使用主队列时遇到了一些问题。我必须对Alamofire使用主队列进行响应方法的事实进行排序,我会看看是否发现潜在的死锁

我会通知你。感谢

1 个答案:

答案 0 :(得分:1)

存在一些小问题,但此操作实现看起来很正确。当然,你应该让你的状态管理线程安全,并且你可以做出其他的风格改进,但我不认为这对你的问题至关重要。

  1. 看起来令人担忧的是addOperations(_:waitUntilFinished:)。你在等哪个队列?如果你从主队列中执行此操作,则会发生死锁(即看起来Alamofire请求永远不会完成)。 Alamofire使用主队列作为其完成处理程序(除非您覆盖queue的{​​{1}}参数),但如果您在主线程上等待,则永远不会发生这种情况。 (顺便说一句,如果你可以重构,那么你永远不会明确地等待"进行操作,这不仅可以避免死锁风险,而且通常也是一种更好的模式。)

  2. 我还注意到您在操作中使用Alamofire请求与后台会话一起使用。后台会话与操作和完成处理程序闭包模式是对立的。在您的应用被放弃后,后台会话将继续,并且您必须完全依赖于在应用启动时首次配置responseJSON时设置的SessionDelegate个闭包。当应用程序重新启动时,您的操作和完成处理程序关闭很快就会消失。

    总而言之,您真的需要后台会话(即在您的应用终止后继续上传和下载)吗?如果是这样,您可能希望失去此完成处理程序和基于操作的方法。如果您在应用终止后不再需要此功能,请不要使用后台会话。配置Alamofire以正确处理后台会话是一项非常重要的练习,所以只有在您绝对需要时才这样做。请记住不要混淆背景会话和Alamofire(以及SessionManager)为您自动执行的简单异步处理。

  3. 你问:

      

    我的应用程序是一个广播播放器,我需要在播放音乐和背景时获取当前正在播放的节目。这就是我需要后台会话的原因。

    如果您希望在应用未运行时继续下载,则需要后台会话。但是,如果您的应用在后台运行,播放音乐,您可能不需要后台会话。但是,如果用户选择下载特定媒体资产,您可能需要后台会话,以便在用户离开应用程序时进行下载,无论应用程序是否正在播放音乐。

      

    事实上,当应用程序处于前台时,我也会使用后台会话进行所有网络连接。我应该避免吗?

    没关系。它有点慢,IIRC,但没关系。

    问题不是您使用后台会话,而是您做错了。基于操作的Alamofire包装对后台会话没有意义。要在后台继续进行会话,您将受限于使用URLSession的方式,即:

    • 在应用未运行时,您无法使用数据任务;只上传和下载任务。

    • 您不能依赖完成处理程序关闭(因为后台会话的全部目的是让您的应用程序终止时继续运行,然后在他们完成后再次启动您的应用程序;但如果应用程序是终止,你的关闭都消失了。)

      您必须仅将基于委托的API用于后台会话,而不是完成处理程序。

    • 您必须实现app delegate方法来捕获系统提供的完成处理程序,当您完成处理后台会话委托调用时调用该处理程序。当URLSession告诉您已完成处理所有后台委托方法时,您必须致电。

    所有这些都是一个沉重的负担,恕我直言。鉴于系统让您的应用程序保持活动状态,您可以考虑使用标准URLSession。如果您要使用后台会话,则可能需要重构所有基于完成处理程序的代码。

      

    我正在使用的等待来自另一个队列,并且从未在主队列中使用过(我知道它是一个线程反模式,我会处理它)。

    好。从来没有使用"等待"仍有严重的代码味道,但如果你100%确信它不会在这里陷入僵局,你可以侥幸逃脱。但这是你应该检查的东西(比如在&#34之后放一些记录声明;等待"并确保你已经过了那条线,如果你还没有证实这一点)。

      

    实际上它在我进行两次网络操作时使用,第二次取决于第二次的结果。我在第一次手术后等待,以避免KVO观察。我应该避免吗?

    就个人而言,我失去了KVO的观察能力,并且只是在操作之间建立URLSessionConfiguration。此外,如果您摆脱了KVO观察,您可以摆脱双KVO通知过程。但我不认为这个KVO的东西是问题的根源,所以也许你推迟了。