LoginSignup
12
6

More than 3 years have passed since last update.

普通にURLSessionとCombineでURLSession

Posted at

概要

URLSessionの通常の書き方、Combineでの書き方について調べました。

題材

QiitaのLGTMの取得APIを例に利用します。
参考: Qiita APIで自分の記事のLGTMとViewを取得する。 - Qiita

現在LGTMが5つの記事のLGTMについて取得します。

https://qiita.com/api/v2/items/a9ead7285d10aadf5643/likes

このURLをGETで叩けば取得可能です。

普通のURLSession

import Foundation

let qiitaURL = URL(string: "https://qiita.com/api/v2/items/a9ead7285d10aadf5643/likes")!

struct LGTM: Codable {
    var created_at: String
    var user: User
}
struct User: Codable {
    var id: String
}

let session = URLSession.shared.dataTask(with: qiitaURL) {data, response, error in
    if let error = error {
        fatalError("Error: \(error.localizedDescription)")
    }
    guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
        fatalError("Error: invalid HTTP response code")
    }
    guard let data = data else {
        fatalError("Error: missing response data")
    }

    do {
        let lgtms = try JSONDecoder().decode([LGTM].self, from: data)
        lgtms.forEach{ lgtm in
            print("username: \(lgtm.user.id), created_at \(lgtm.created_at)")
        }
    }
    catch {
        print("Error: \(error.localizedDescription)")
    }
}
session.resume()

let sessionの型はURLSessionDataTaskです。
URLSessionDataTaskのresumeを呼び出すことで通信します。

URLSessionのメソッドdataTaskの定義は以下です。


open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

CombineでURLSession

Combineで記述すると以下です。

import Foundation
import Combine

var cancellable = URLSession.shared
    .dataTaskPublisher(for: qiitaURL)
    .tryMap { element -> Data in
        guard let httpResponse = element.response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return element.data
    }
    .decode(type: [LGTM].self, decoder: JSONDecoder())
    .sink(receiveCompletion: {
        print("complete! \($0)")
    }, receiveValue: { lgtms in
        lgtms.forEach{ lgtm in
            print("username: \(lgtm.user.id), created_at \(lgtm.created_at)")
        }
    })

少し処理が変わってしまってますが、Combineの方がcompletionHandlerでの処理が少なくなり、見通しがよくなるように感じます。

Tips

JsonDecoderであるのかないのかわからない名前を指定する。

Jsonのフィールド名にないものを指定してしまうと、
Error: The data couldn’t be read because it is missing.とJsonDecoderがエラーを出します。

例えば次のようにstructを定義した場合です。

struct User: Codable {
    var id: String
    var aaa: String
}

こういう時は、ないかもしれないフィールド名をOptionalにすることでnilが入り、エラーを防ぐことが可能です。

struct User: Codable {
    var id: String
    var aaa: String?
}

URLSession.sharedとは?

URLSession.sharedはサクッと書きたいときに使うsingletonです。
デフォルトの挙動で動くため、高度な処理ができません。

  • サーバーからの逐次のデータ取得はできない
  • 大きくカスタマイズできない
  • 認証関係に制限あり
  • アプリが動いていないときにダウンロードやアップロードできない

shared | Apple Developer Documentation

element -> Data とは何をしている?

クロージャの引数を明示しているだけです。
-> Dataの部分がなくても動きます。

Closures — The Swift Programming Language (Swift 5.3)

配列の要素をひとつ1つ処理したい

CombineでURLSessionのsubscribe処理は、現在forEachによる入れ子構造となっています。

.decode(type: [LGTM].self, decoder: JSONDecoder())
.sink(receiveValue: { lgtms in
        lgtms.forEach{ lgtm in
            print("username: \(lgtm.user.id), created_at \(lgtm.created_at)")
        }
    }

こちらの入れ子をなくすには、flatMapが利用できます。

.decode(type: [LGTM].self, decoder: JSONDecoder())
.flatMap {$0.publisher}
.sink(receiveCompletion: {
    print("complete! \($0)")
}, receiveValue: { lgtm in
    print("username: \(lgtm.user.id), created_at \(lgtm.created_at)")
})

終わり

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6