2016-10-14 18 views
3

Note:雖然在SO中還存在其他類似的問題,但其中沒有一個作者似乎自己控制Operation的生命週期。請在提及另一個問題之前仔細閱讀。Alamofire請求不在NS內運行完成塊

我在Swift 3.0中創建了一個[NS]操作來下載,解析和緩存核心數據中的一些數據。

起初,我在手術中使用了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

問題是您有其他東西阻塞主線程,responseJSON用於其關閉,導致死鎖。您可以快速確認這一點,將responseJSON替換爲.responseJSON(queue: .global())以使Alamofire使用主隊列以外的隊列,您將看到此行爲更改。但是,如果您這樣做(僅用於診斷目的),則應該將其更改回來,然後將注意力轉到識別並消除阻塞主線程的任何內容(即不要在主線程上等待),因爲您絕不應阻塞主線程。


您提到您致電waitUntilAllOperationsAreFinished。雖然這是等待一系列操作完成的令人陶醉的簡單解決方案,但您絕對不應該在主線程中這樣做。主線程不應該被阻塞(或者至少不會超過幾毫秒)。它可能會導致一個不合標準的UX(應用程序凍結),並且您的應用程序容易被「看門狗」進程立即終止。我懷疑Alamofire的作者覺得如此舒適以便默認將他們的完成處理程序派發到主隊列中的原因之一是,這不僅經常有用和方便,而且他們知道永遠不會阻塞主線程。

當使用操作隊列,典型的模式,以避免以往任何時候都等待是使用完成操作:

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) 

可以實現類似的,如果你使用的調度組,調度組「通知」的東西(雖然,通常,如果你使用操作隊列,爲了一致性的緣故,你通常會保持操作隊列模式)。

如果你想打電話waitUntilAllOperationsAreFinished,在技術上你可以這樣做,但是你應該這樣做,如果你派遣「等待」到某個背景隊列(例如一個全局隊列,但顯然不是你自己的操作隊列上添加了所有這些操作)。但我認爲這是一種浪費的模式(當你有一個完美的機制來指定一個完成操作時,爲什麼要等待一些全局工作線程等待操作完成)。

+0

感謝您花時間回答@Rob。我做了你的建議,沒有改變。我會看看'阻塞主線程'的事情,但我不明白可能導致什麼。你看到的代碼是整個Operation.There沒有更多的。 – reydelleon

+0

好@Rob,我認爲你指出了正確的方向,但還有更多的工作要做。我在負責排隊任務的方法中使用'queue.waitUntilAllOperationsAreFinished()'。如果我評論說,隊列不會停頓,但實際上我需要等待所有操作完成,然後再調用完成處理程序。有沒有辦法解決這個問題? – reydelleon

+0

你的回答最終給我帶來了正確的方向。我只是編輯它來添加一個鏈接到一個問題,解決了原始問題,我只有在分析問題後才發現,考慮到你的指針。 – reydelleon