2017-04-10 121 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的東西是問題的根源,所以也許你會推遲這一點。

相關問題