2017-04-10 118 views
1

我在使用OperationOperationQueue的Alamofire时遇到问题。Alamofire在应用程序生命周期中阻止请求

我有一个名为OperationQueueNetworkingQueue我推一些操作(包装AlamofireRequest)进去,一切工作正常,但在应用程序生活,在某一时刻都Alamofire请求不被发送。我的队列越来越大,没有任何要求结束。

我没有计划随时重现它。

有没有人有帮助我的线索?

下面是代码的示例

的BackgroundAlamoSession

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

AbstractOperation.swift

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 
    } 

} 

的具体操作实施

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 
     } 
    } 
} 

Ť他NetworkQueue

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

我如何使用它的另一个操作,等待的结果

let getObjectOperation:FetchObject = FetchObject(30) 
SaootiQueue.networkQueue.addOperations([getObjectOperation], waitUntilFinished: true) 

我如何使用它使用志愿

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 
    } 
} 

一些澄清的基本操作:

我的应用程序阳离子是一个收音机,我需要,当播放音乐和背景时,获取正在播放的节目。这就是为什么我需要背景Session。

事实上,我也使用后台会话来处理应用程序在前台时执行的所有联网。我应该避免吗?

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

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


附加编辑:

当我说“我的队列越来越大,并没有要求去年底”,这意味着,在应用程序的LiveCycle一个时刻,随机的时刻(我无法找到每次重现的方法),Alamofire请求没有达到响应方法。

因为Operation包装不会结束并且队列正在增长。

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

我会随时向您通报。谢谢

+0

感谢您的评论。我添加了一些示例代码。我希望它会提供更多的信息,如果有什么问题在检测它。 –

+0

'operation.first'是什么?我没有看到任何'第一个'属性... – Rob

+0

当我试图删除所有业务代码以专注于问题时,这是我的错。首先是业务代码的一部分。事实上,所有的操作都是超载的,以提出不同的请求。第一个参数在重载类上。我将它从示例代码 –

回答

1

有一些小问题,但是这个操作实现看起来基本正确。当然,你应该让你的状态管理线程安全,并且还可以做其他的风格改进,但是我认为这对你的问题并不重要。

  1. 看起来令人担忧的是addOperations(_:waitUntilFinished:)。你在哪个队列等待?如果你在主队列中这样做,你会死锁(即它看起来像Alamofire请求永远不会完成)。 Alamofire为其完成处理程序使用主队列(除非您覆盖responseJSONqueue参数),但如果您在主线程中等待,则永远不会发生。 (顺便说一句,如果你可以重构,所以你永远不会明确地“等待”操作,这不仅避免了死锁风险,而且是一种更好的模式。)

  2. 我还注意到你正在使用Alamofire与后台会话一起运行的请求。后台会话与操作和完成处理程序关闭模式相对。在您的应用程序被抛弃后,后台会话将继续进行,您必须完全依赖您在应用程序启动时首次配置SessionManager时设置的SessionDelegate关闭。当应用程序重新启动时,您的操作和完成处理程序关闭已久。底线,你真的需要后台会话(即上传和下载,在你的应用程序终止后继续)吗?如果是这样,你可能想失去这个完成处理程序和基于操作的方法。如果您在应用程序终止后不需要继续此操作,请不要使用后台会话。配置Alamofire以正确处理后台会话不是一件容易的事情,所以只有在绝对需要时才能这样做。请记住不要混淆后台会话和Alamofire(和URLSession)为您自动执行的简单异步处理。


你问:

我的应用程序是一个电台播放器,我需要,在播放音乐和背景,以获取当前正在播放的节目。这就是为什么我需要背景Session。

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

事实上,我也使用后台会话为应用程序是前景时我做的所有网络。我应该避免吗?

这很好。这是一个慢一点,IIRC,但没关系。

问题不是你使用后台会话,而是你做错了。在后台会话中,基于操作的Alamofire包装并不合理。对于会议在后台进行,您被限制为如何使用URLSession,即:

  • 而应用程序没有运行,则无法使用数据的任务;只上传和下载任务。

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

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

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

所有这些都是一个很大的负担,恕我直言。鉴于该系统让您的应用程序保持活跃的背景音乐,您可以考虑使用标准的URLSessionConfiguration。如果您打算使用后台会话,则可能需要重构所有基于完成处理程序的代码。

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

好。使用“等待”仍然会产生严重的代码异味,但如果您100%确信它不会在这里陷入僵局,那么您可以避开它。但是这是你真正应该检查的东西(例如,在“等待”之后放置一些日志记录并确保你已经越过那条线,如果你还没有确认的话)。

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

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

相关问题