URLからJSONを読み取るための汎用ヘルパー関数でエラーが発生しました

Aug 23 2020

私のプロジェクトの1つでは、複数のビューの複数のURLからいくつかの構造体にJSONを読み取りたいので、小さいが一般的なヘルパー関数を作成することにしました。

この関数は、たとえば呼び出す必要があります

ビュー1:

let call1 = Bundle.main.decode(iobrokerSection.self, from: "http://192.168.1.205:8087/get/javascript.0.Fahrzeiten.Dauer")

ビュー2:

let call2 = Bundle.main.decodeURL(iobrokerweather.self, from: "http://192.168.1.205:8087/get/javascript.0.Fahrzeiten.Weather")

等々。

最初の例では、構造体iobrokerSectionは次のとおりです。

struct iobrokerNumberDataPoint: Codable {
    var val: Int
    var ack: Bool
    var ts: Int
    var q: Int
    var from: String
    var user: String
    var lc: Int
    var _id: String
    var type: String
}

そして、これが私のヘルパー関数です

extension Bundle {
    func decodeURL<T: Decodable>(_ type: T.Type, from urlString: String) -> T {
        guard let url = URL(string: urlString) else {
            fatalError("Placeholder for a good error message")
        }
        
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            guard let loaded = try? JSONDecoder().decode(T.self, from: data!) else {
                fatalError("Placeholder for a good error message")
            }
        }.resume()

        return loaded
    }
}

「returnloaded」でコンパイラメッセージ「Bool型のreturn式をT型を返すように変換できません」が表示される理由を理解していると思います。

しかし、これを修正する方法がわかりません。

誰かが私にヒントをくれますか?

回答

LeoDabus Aug 23 2020 at 15:49

まず、すべての構造、クラス、プロトコルに大文字で始まる名前を付けるのがSwiftの命名規則です。次に、非同期メソッドが終了して値を返すのを待つことはできません。メソッドに完了ハンドラーを追加する必要があります。第3に、一部のデータを取得することだけを目的としている場合は、URLRequestを使用する必要はありません。文字列の代わりにURLを使用して、メソッドにURLを渡すことができます。4番目に、返されたデータを強制的にアンラップしないでください。nilになる可能性があります。オプションのデータを安全にアンラップし、エラーが発生した場合はそれを完了ハンドラーに渡す必要があります。デコードメソッドは次のようになります。

extension Bundle {
    func decode<T: Decodable>(_ type: T.Type, from url: URL, completion: @escaping (T?, Error?) -> Void) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else {
                completion(nil, error)
                return
            }
            do {
               try completion(JSONDecoder().decode(T.self, from: data), nil)
            } catch {
                completion(nil, error)
            }
        }.resume()
    }
}

このメソッドを呼び出すときは、結果のクロージャー内で非同期の結果を取得する必要があります。

struct IOBrokerNumberDataPoint: Codable {
    var val: Int
    var ack: Bool
    var ts: Int
    var q: Int
    var from: String
    var user: String
    var lc: Int
    var id: String
    var type: String
    enum CodingKeys: String, CodingKey {
        case val, ack, ts, q, from, user, lc, id = "_id", type
    }
}

let url = URL(string: "http://192.168.1.205:8087/get/javascript.0.Fahrzeiten.Dauer")!
Bundle.main.decode(IOBrokerNumberDataPoint.self, from: url) { brokerNumberDataPoint, error in
    guard let brokerNumberDataPoint = brokerNumberDataPoint else {
        print("error", error ?? "")
        return
    }
    print("brokerNumberDataPoint", brokerNumberDataPoint)
    // use brokerNumberDataPoint here
}

もう1つのオプションは、Swift5のResult ジェネリック列挙を使用することです。

extension Bundle {
    func decode<T: Decodable>(from url: URL, completion: @escaping (Result<T, Error>) -> Void) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                if let error = error { completion(.failure(error)) }
                return
            }
            do {
                try completion(.success(JSONDecoder().decode(T.self, from: data)))
            } catch {
                completion(.failure(error))
            }

        }.resume()
    }
}

使用法:

let url = URL(string: "http://192.168.1.205:8087/get/javascript.0.Fahrzeiten.Dauer")!

Bundle.main.decode(from: url) { (result: Result<IOBrokerNumberDataPoint, Error>) in
    switch result {
    case let .success(brokerNumberDataPoint):
        print("brokerNumberDataPoint", brokerNumberDataPoint)
        // use brokerNumberDataPoint here
    case let .failure(error):
        print("error:", error)
    }
    
    
}