

【やりたいこと】  
ViewControllerクラスの変数dataにGetDataFromAPIクラスのgetDataFromAPI関数で取得したデータを格納したい。

【つまづいている点】  
クロージャ内の変数の返し方がわからずうまくいっていません。

一週間ほどこの部分の実装が出来ず、開発が止まっております。  
よろしくお願いいたします

ViewController.swift

[code]

    import UIKit
    
    class ViewController: UIViewController {
        var data:String = ""
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            let getDataObject = GetDataFromAPI()
            let returnData = getDataObject.getDataFromAPI()
            data = returnData
            print("data:\(data)")
    
        }
    
    
    }
    
[/code]

GetData.swift

[code]

    import Foundation
    
    struct JsonData:Codable{
        //今回返ってくるJSONは「{"key":"value"}」
        let key:String
    }
    
    class GetDataFromAPI {
        var formatedData:String = ""
    
        func getDataFromAPI() -> String{
            let url: URL = URL(string:"http://127.0.0.1:5000/study")!
            let dispatchGroup = DispatchGroup()
    
            dispatchGroup.enter()
            let task: URLSessionTask = URLSession.shared.dataTask(with: url, completionHandler: {data, response, error in
                let decoder: JSONDecoder = JSONDecoder()
                do {
                    let data:JsonData = try decoder.decode(JsonData.self, from: data!)//取ってきたデータが格納されている
    
                    print("データの取得しました")
                    self.formatedData = data.key
                    print(self.formatedData)
    
                } catch {
                    print("error:", error.localizedDescription)
                }
            })
            task.resume()//実行する
            return self.formatedData
        }
    
    }
    
[/code]


CC BY-SA 4.0
2019-02-12T10:52:56.767
52727
2019-02-12T20:16:06.790
32146
question
swift ios swift4
Swift4でのURLSessionTaskを用いたAPIからのデータ取得について。


`URLSessionTask`に代表される非同期のAPIを使いこなすコツは「値を(戻り値として)返す」なんて発想を捨ててしまうことです。あなたのアプリにとって重要なのはサーバから取得した値を確実に利用することのはずで、「非同期に取得した値を無理やり同期的に戻り値として返すような変コーディングをすること」が開発の目的では無いはずです。

非同期サポートの標準的な道具立てが揃っていない現在のSwiftでは、このような場合には完了ハンドラパターンを採用するのが一般的です。

[code]

    class GetDataFromAPI {
    
        func getDataFromAPI(completion: @escaping (String)->Void) { //<-自前の完了ハンドラ引数を付け加える
            let url: URL = URL(string:"http://127.0.0.1:5000/study")!
    
            let task: URLSessionTask = URLSession.shared.dataTask(with: url, completionHandler: {data, response, error in
                let decoder: JSONDecoder = JSONDecoder()
                do {
                    let data: JsonData = try decoder.decode(JsonData.self, from: data!)//取ってきたデータが格納されている
    
                    print("データの取得しました")
                    print(data.key)
                    completion(data.key) //<- `URLSession.dataTask(with:completionHandler:)`の完了ハンドラの中で、自前の完了ハンドラを呼ぶ
    
                } catch {
                    print("error:", error.localizedDescription)
                    completion("") //<- 同上(エラー時のことも考えておきましょう)
                }
            })
            task.resume()//実行する
            //↑「`task.resume()`を呼んだら後は何もしてはいけない」と思っておいた方が良い
        }
    
    }
    
[/code]

(`formatedData`なんてインスタンス変数を持たせると複数のリクエストを同時に飛ばす場合などに問題になるので、消してしまいました。)

呼び出し側はこんな感じになります。

[code]

        let getDataObject = GetDataFromAPI()
        getDataObject.getDataFromAPI {returnData in
            self.data = returnData //<-実際のコードでは`String`型の変数に`data`なんて命名は避けましょう
            print("data:\(self.data)")
        }
    
[/code]

類似の質問は過去幾度もこの日本語版スタック・オーバーフローやネット上のブログや掲示板等で取り上げられており、そのうちのいくつかには「非同期に取得した値を無理やり同期的に戻り値として返す」ようなやり方が回答に含まれている場合がありますが、それらは皆「コードが複雑化する割には特定の場合にしか動かない」「通信状態の不安定な環境でアプリが固まってしまう」等々の欠点があるものです。

少しでも早く正しいやり方に慣れていってください。


CC BY-SA 4.0
2019-02-12T19:57:49.943
52737
2019-02-12T20:16:06.790
2019-02-12T20:16:06.790
13972
13972
52727
answer