概要
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)")
})