2017-12-28 26 views
0

概念的に単純な問題に悩まされました。何が起こっているのは、ダウンロード操作の完了ハンドラが終了する前に解析操作が実行されていることです。その結果、解析するデータはありません。次のコードをファイルにドロップして実行することができます。SwiftでOperationQueueを使用してJSONをダウンロードして解析する方法

パーズ操作を実行する前にダウンロードが完了していることを確認するにはどうすればよいですか?

import UIKit 

let search = "https://api.nal.usda.gov/ndb/search/?format=json&q=butter&sort=n&max=25&offset=0&api_key=DEMO_KEY" 

class ViewController: UIViewController { 



    override func viewDidLoad() { 
     super.viewDidLoad() 

     let fetch = FetchNBDNumbersOperation() 
     let parse = NDBParseOperation() 

     // 1 
     let adapter = BlockOperation() { [unowned parse, unowned fetch] in 
      parse.data = fetch.data 
     } 

     // 2 
     adapter.addDependency(fetch) 
     parse.addDependency(adapter) 

     // 3 
     let queue = OperationQueue() 
     queue.addOperations([fetch, parse, adapter], waitUntilFinished: true) 
    } 
} 

class FetchNBDNumbersOperation: Operation { 

    var data: Data? 

    override func main() { 
     let url = URL(string: search)! 
     let urlSession = URLSession.shared 
     let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in 
      guard let jsonData = jsonData, let response = response else { 
       debugPrint(error!.localizedDescription) 
       return 
      } 
      self.data = jsonData 
      print("Response URL: \(String(describing: response.url?.absoluteString))") 
     } 
     dataTask.resume() 
    } 
} 

class NDBParseOperation: Operation { 

    var data: Data? 
    var nbdNumbers = [NBDNumber]() 

    override func main() { 
     let decoder = JSONDecoder() 
     do { 
      guard let jsonData = self.data else { 
       fatalError("No Data") 
      } 
      let dictionary = try decoder.decode([String: USDAFoodSearch].self, from: jsonData) 
      for (_, foodlist) in dictionary { 
       for food in foodlist.item { 
        print("\(food.name) \(food.ndbno) \(food.group)") 
        let nbdNumber = NBDNumber(name: food.name, nbdNo: food.ndbno) 
        nbdNumbers.append(nbdNumber) 
       } 
      } 
     } catch { 
      print(error.localizedDescription) 
     } 
    } 
} 

struct NBDNumber { 
    var name: String 
    var nbdNo: String 
} 

struct USDAFoodSearch: Decodable { 
    let q: String 
    let sr: String 
    let ds: String 
    let start: Int 
    let end: Int 
    let total: Int 
    let group: String 
    let sort: String 
    let item: [USDAFood] 

    struct USDAFood: Decodable { 
     let offset: Int  //Position in Array 
     let group: String 
     let name: String 
     let ndbno: String 
     let ds: String 
    } 
} 
+1

私の謙虚な意見では、私はあなただけ** 1 **と「構文解析を」フェッチ 'を含む操作を含めるには、この再設計をすべきだと思います。したがって、データを取得してデータを解析し終えたら、両方とも互いに関連しているため、両方の操作を別々に行う必要はありません。 –

答えて

0

に便利ですホープのコード例です。以下のクラスでサブクラスフェッチオペレーションを実行します。そして、オペレーションがFetch Op完了ハンドラの終わりに完了したことを伝えます。

class FetchNBDNumbersOperation: AsynchronousOperation { 

    var data: Data? 

    override func main() { 
     super.main() 

     let url = URL(string: search)! 
     let urlSession = URLSession.shared 
     let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in 
      guard let jsonData = jsonData, let response = response else { 
       debugPrint(error!.localizedDescription) 
       return 
      } 
      self.data = jsonData 
      print("Response URL: \(String(describing: response.url?.absoluteString))") 
      self.state = .finished 
     } 
     dataTask.resume() 
    } 
} 

ここ非同期サブクラス見つかり:https://gist.github.com/Sorix/57bc3295dc001434fe08acbb053ed2bc

/// Subclass of `Operation` that add support of asynchronous operations. 
/// ## How to use: 
/// 1. Call `super.main()` when override `main` method, call `super.start()` when override `start` method. 
/// 2. When operation is finished or cancelled set `self.state = .finished` 
class AsynchronousOperation: Operation { 
    override var isAsynchronous: Bool { return true } 
    override var isExecuting: Bool { return state == .executing } 
    override var isFinished: Bool { return state == .finished } 

    var state = State.ready { 
     willSet { 
      willChangeValue(forKey: state.keyPath) 
      willChangeValue(forKey: newValue.keyPath) 
     } 
     didSet { 
      didChangeValue(forKey: state.keyPath) 
      didChangeValue(forKey: oldValue.keyPath) 
     } 
    } 

    enum State: String { 
     case ready = "Ready" 
     case executing = "Executing" 
     case finished = "Finished" 
     fileprivate var keyPath: String { return "is" + self.rawValue } 
    } 

    override func start() { 
     if self.isCancelled { 
      state = .finished 
     } else { 
      state = .ready 
      main() 
     } 
    } 

    override func main() { 
     if self.isCancelled { 
      state = .finished 
     } else { 
      state = .executing 
     } 
    } 
} 
+0

ただし、素晴らしいソリューションはsuper.start()を呼び出すことはなく、super.main()は必要ありません。あなたは実際にコードでこれを行うのではなく、コメントの中でそれを持っています。 –

0

あなたはこれを過度に複雑さ、さらにはデータタスクが非同期で実行されるようにOperationQueueを使用する必要はありません。

class FetchAndParse { 
    var data: Data? 
    var nbdNumbers = [NBDNumber]() 

    func fetch() { 
     let url = URL(string: search)! 
     let urlSession = URLSession.shared 
     let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in 
      guard let jsonData = jsonData, let response = response else { 
       debugPrint(error!.localizedDescription) 
       return 
      } 
      self.data = jsonData 
      print("Response URL: \(String(describing: response.url?.absoluteString))") 

      self.parse() 
     } 
     dataTask.resume() 
    } 

    func parse() { 
     let decoder = JSONDecoder() 
     do { 
      guard let jsonData = self.data else { 
       fatalError("No Data") 
      } 
      let dictionary = try decoder.decode([String: USDAFoodSearch].self, from: jsonData) 
      for (_, foodlist) in dictionary { 
       for food in foodlist.item { 
        print("\(food.name) \(food.ndbno) \(food.group)") 
        let nbdNumber = NBDNumber(name: food.name, nbdNo: food.ndbno) 
        nbdNumbers.append(nbdNumber) 
       } 
      } 
      print ("Finished With \(nbdNumbers.count) Items") 
     } catch { 
      print(error.localizedDescription) 
     } 
    } 
} 

あなたは、このように使用します:

あなたはちょうどこのような何かを行うことができ

let fp = FetchAndParse() 
print ("Before Fetch") 
fp.fetch() 
print ("After Fetch") 

あなたが「前に取得」と「取得後」が表示されますことを実行する場合ダウンロードの前にメッセージが表示され、解析が完了し、取得が正しく行われた後に解析が行われます。

もちろん、クラスがすべてが完了していることを通知するように更新する必要があります。つまり、補完ハンドラまたは代理人かもしれませんが、残しておきます。

+0

提案に感謝しかし、最初の2つに依存する他の操作があり、それなしでは維持不能な混乱に変わるため、操作が必要です。 – Aaronium112

+0

あなたは、もともと、情報を半分にすることで問題を解決していることに言及していたはずです。 –

1

フェッチ操作では、URLSessionDataTaskを再開します。この時点では、他のスレッドで何が起きているのかわからず、本質的に並行操作を気にしないので、操作は完了したと判断し、従属(adapter)を開始します。一方、URLSessionDataTaskはまだ別のスレッドで実行されています。

非同時操作のためにNSOperation

上のAppleのドキュメントから、あなたは通常、1つの方法だけオーバーライドします。同時に

  • main()

URLSessionDataTaskの実行をので、あなたNSOperationでそれらを包むためにより多くの仕事をしています。

start()

isAsynchronous

isExecuting

isFinished

...最低でも次のメソッドとプロパティをオーバーライドします。同時操作をラップするためにあなたがする必要があります

NSOperationdocsの詳細についてはかなり詳しく説明されていますが、main()の代わりにstart()をオーバーライドし、実装の動作ステータスを最新の状態に保つ必要があります。

+0

役立つ手掛かり。ありがとう。 – Aaronium112

0

ちょうどmain操作キューの方法を使用しています。
そしてOperation QueueisExecuting必要

jjatie として、あなたが運転状態に関する操作キューに通知されていないが、操作終了か

はあなたに私の提案は

ある 実行についてのキューをしてください通知する KVCをisFinishedこのような複雑な操作を行う前にドキュメントを読んでください。ここ

class WSOperations: Operation { 

    private var _executing = false 
    private var _finished = false 
    private var showHUD:HUDFlag = .show 

    override var isExecuting: Bool { 
     get { 
      return _executing 
     } set { 
      willChangeValue(forKey: "isExecuting") 
      _executing = newValue 
      didChangeValue(forKey: "isExecuting") 

     } 

    } 

    override var isFinished: Bool { 
     get { 
      return _finished 
     } set { 
      willChangeValue(forKey: "isFinished") 
      _finished = newValue 
      didChangeValue(forKey: "isFinished") 
     } 
    } 



    override func start() { 
     if isCancelled { 
      isFinished = true 

      return 
     } 

     isExecuting = true 

     func completeOperation() { 
      isFinished = true 
      isExecuting = false 
      Logger.log(message: "Operation finished") 
     } 

     //Your request 
      request = DataManager.sharedManager.getRequest(showHUD: showHUD, success: { (success, response) in 
       if let t = self.finishedBLock { 
        t.success(success, response) 
       } 
       completeOperation() 
      }, failure: { (error) in 
       if let t = self.finishedBLock { 
        t.failure(error) 
       } 
       completeOperation() 

      }) 


    } 
    override func cancel() { 
     super.cancel() 
     if isExecuting { 
      isFinished = true 
      isExecuting = false 
     } 

     request?.cancel() 
    } 
} 

が、ここでの答えだあなた

関連する問題