我使用Operation
和OperationQueue
在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使用主队列进行响应方法的事实进行排序,我会看看是否发现潜在的死锁
我会通知你。感谢
答案 0 :(得分:1)
存在一些小问题,但此操作实现看起来很正确。当然,你应该让你的状态管理线程安全,并且你可以做出其他的风格改进,但我不认为这对你的问题至关重要。
看起来令人担忧的是addOperations(_:waitUntilFinished:)
。你在等哪个队列?如果你从主队列中执行此操作,则会发生死锁(即看起来Alamofire请求永远不会完成)。 Alamofire使用主队列作为其完成处理程序(除非您覆盖queue
的{{1}}参数),但如果您在主线程上等待,则永远不会发生这种情况。 (顺便说一句,如果你可以重构,那么你永远不会明确地等待"进行操作,这不仅可以避免死锁风险,而且通常也是一种更好的模式。)
我还注意到您在操作中使用Alamofire请求与后台会话一起使用。后台会话与操作和完成处理程序闭包模式是对立的。在您的应用被放弃后,后台会话将继续,并且您必须完全依赖于在应用启动时首次配置responseJSON
时设置的SessionDelegate
个闭包。当应用程序重新启动时,您的操作和完成处理程序关闭很快就会消失。
总而言之,您真的需要后台会话(即在您的应用终止后继续上传和下载)吗?如果是这样,您可能希望失去此完成处理程序和基于操作的方法。如果您在应用终止后不再需要此功能,请不要使用后台会话。配置Alamofire以正确处理后台会话是一项非常重要的练习,所以只有在您绝对需要时才这样做。请记住不要混淆背景会话和Alamofire(以及SessionManager
)为您自动执行的简单异步处理。
你问:
我的应用程序是一个广播播放器,我需要在播放音乐和背景时获取当前正在播放的节目。这就是我需要后台会话的原因。
如果您希望在应用未运行时继续下载,则需要后台会话。但是,如果您的应用在后台运行,播放音乐,您可能不需要后台会话。但是,如果用户选择下载特定媒体资产,您可能需要后台会话,以便在用户离开应用程序时进行下载,无论应用程序是否正在播放音乐。
事实上,当应用程序处于前台时,我也会使用后台会话进行所有网络连接。我应该避免吗?
没关系。它有点慢,IIRC,但没关系。
问题不是您使用后台会话,而是您做错了。基于操作的Alamofire包装对后台会话没有意义。要在后台继续进行会话,您将受限于使用URLSession
的方式,即:
在应用未运行时,您无法使用数据任务;只上传和下载任务。
您不能依赖完成处理程序关闭(因为后台会话的全部目的是让您的应用程序终止时继续运行,然后在他们完成后再次启动您的应用程序;但如果应用程序是终止,你的关闭都消失了。)
您必须仅将基于委托的API用于后台会话,而不是完成处理程序。
您必须实现app delegate方法来捕获系统提供的完成处理程序,当您完成处理后台会话委托调用时调用该处理程序。当URLSession
告诉您已完成处理所有后台委托方法时,您必须致电。
所有这些都是一个沉重的负担,恕我直言。鉴于系统让您的应用程序保持活动状态,您可以考虑使用标准URLSession
。如果您要使用后台会话,则可能需要重构所有基于完成处理程序的代码。
我正在使用的等待来自另一个队列,并且从未在主队列中使用过(我知道它是一个线程反模式,我会处理它)。
好。从来没有使用"等待"仍有严重的代码味道,但如果你100%确信它不会在这里陷入僵局,你可以侥幸逃脱。但这是你应该检查的东西(比如在&#34之后放一些记录声明;等待"并确保你已经过了那条线,如果你还没有证实这一点)。
实际上它在我进行两次网络操作时使用,第二次取决于第二次的结果。我在第一次手术后等待,以避免KVO观察。我应该避免吗?
就个人而言,我失去了KVO的观察能力,并且只是在操作之间建立URLSessionConfiguration
。此外,如果您摆脱了KVO观察,您可以摆脱双KVO通知过程。但我不认为这个KVO的东西是问题的根源,所以也许你推迟了。