2016-10-14 10 views
3

Note:これには他にも同様の質問がありますが、いずれも著者はOperationのライフサイクルを制御しているようです。別の質問を参照する前に読んでください。AlamofireリクエストはNSOperation内の完了ブロックを実行していません

Core Dataでデータをダウンロード、解析、キャッシュするためにSwift 3.0で[NS] Operationを作成しました。

最初は、作業中にmain()メソッドを使用して作業を実行し、うまくいきました。今度は、このステップで取得した各デバイスに関する情報を取得するために、いくつかのタスクを実行する必要があります。このために、他の情報を取得しようとする前に、デバイスが実際にコアデータに入っていることを確認する必要があります。そのため、依存する要求を開始する前に、すべてのデバイスが安全であり、キャッシュ内で健全であることがいつタスクが完了したかを確認する必要があります。

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の作者が、デフォルトで完了ハンドラをメインキューにディスパッチするのがとても快適だと感じた理由の1つは、便利で便利なだけでなく、決してメインスレッドをブロックしないことを知っていることです。

操作キューを使用する場合は、これまで待って回避するために、典型的なパターンが完了操作を使用することです:

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にお返事いただきありがとうございます。私はあなたが示唆したことをしたし、変化はなかった。私は 'main thread'を阻止しようとしていますが、何が原因か分かりません。あなたが見るコードはOperation全体です。それ以上はありません。 – reydelleon

+0

OK @Rob、あなたは正しい方向を指していると思うが、やるべきことはまだまだある。タスクをキューイングするメソッドで 'queue.waitUntilAllOperationsAreFinished()'を使用しています。私がコメントした場合、キューは停止しませんが、実際には完了ハンドラを呼び出す前にすべての操作が完了するのを待つ必要があります。その周りに道がありますか? – reydelleon

+0

答えが私の最後の正しい方向に送られました。私はちょうどあなたのポインタを考慮に入れて問題を分析した後に見つけた、元の問題を解決する質問へのリンクを追加するためにそれを編集しました。 – reydelleon

関連する問題