iOS APIクライアントの作り方【Concurrency】
個人でモバイルアプリを開発している藤原です。
今日は、iOSでのAPIクライアントの作り方について見ていきましょう。想定している読者は、SwiftでiOSアプリを作り始めた初級クラスのエンジニアを対象にしています。
はじめに
またこの記事は勉強記事です。(Mobile App Hubでの勉強会で使いました)。正確な表現より初心者同士の勉強としての言葉づかいが多くなっています。ご了承下さい。
APIClientとは何か
iOSやAndroidといったモバイルアプリにおいて、APIを効果的に呼び出すための一連のコードのことを、私たちはAPIクライアントと呼んでいます。
ViewControllerからAPIを直接叩くことも可能ですが、色々とまずいらしいです。自分の理解の範囲では第一にDRY原則[1]の観点から望ましくないし、保守先の観点からもダメだと思います。
APIクライアントとして、外部との通信をするコードを一つにまとめることでプロダクトの開発をより安全でスピーディーなものにできると考えています。
ChatGPTによる説明
モバイルアプリ開発において、APIを呼び出すためのコードを「APIクライアント」と呼びます。APIクライアントは、外部のAPIサーバーと通信するためのインターフェースを提供し、ネットワークリクエストの管理、データの取得、エラーハンドリングなどを担当します。
どうしてconcurrency使うといい感じなのか
モバイルアプリにおけるAPIクライアントでは、一般的に多くのサードパティライブラリが使われています。[2]
サードパーティライブラリは豊富に存在しますが、Apple公式の[Concurrency(コンカレンシー)](https://developer.apple.com/documentation/swift/concurrency)を使うことで、現代的でかっここいいAPIクライアントが実装できるかなと考えました。
どのようにかっこいいか、以下にまとめました。
Conqurrencyの利点
- パフォーマンスの向上: 非同期処理により、APIリクエストやデータ処理などをバックグラウンドで行うことで、アプリの応答性とパフォーマンスを向上させることができます。
- コードの可読性と保守性: Modern Swift Concurrencyは、非同期処理をより簡潔かつ直感的に記述できるため、コードの可読性と保守性が向上します。
- 安全な並行処理: メモリ安全性やデータ競合のリスクを減少させることができます。
自分には特に利点2が刺さりました。Flutterで開発をしていたので、async/awaitで非同期処理が書けることが魅力的に感じました。
Swift ConcurrencyでAPIClientを書いてみる
まずはChatGPTに書かせたコードのポン出しをもとにみてみましょう。
import Foundation
// APIのレスポンスを格納するための構造体(サンプル)
struct ApiResponse: Codable {
let id: Int
let login: String
// 他のフィールド...
}
// エラーを表すカスタムenum
enum ApiError: Error {
case invalidUrl
case invalidResponse
case serverError(statusCode: Int)
case decodingError
case unknownError
}
// HTTPメソッドを表すenum
enum HttpMethod {
case get
case post
// 他のHTTPメソッドも追加可能
}
// APIクライアントのクラス
class ApiClient {
let urlString: String
let method: HttpMethod
// イニシャライザでURLとHTTPメソッドを受け取る
init(urlString: String, method: HttpMethod) {
self.urlString = urlString
self.method = method
}
// URLRequestを生成する
private func createRequest() -> URLRequest? {
guard let url = URL(string: urlString) else { return nil }
var request = URLRequest(url: url)
request.httpMethod = method == .get ? "GET" : "POST"
return request
}
// APIリクエストを非同期で実行し、結果を返す
func fetchData() async throws -> ApiResponse {
guard let request = createRequest() else {
throw ApiError.invalidUrl
}
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw ApiError.serverError(statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0)
}
let decodedData = try JSONDecoder().decode(ApiResponse.self, from: data)
return decodedData
}
}
// 使用例
let apiClient = ApiClient(urlString: "https://api.github.com/users/mutt5", method: .get)
Task {
do {
let response = try await apiClient.fetchData()
print("API Response: \(response)")
} catch {
print("API Request Error: \(error)")
}
}
ApiClientのインスタンス化に際して、URLと取得方法を渡しています。(ここでは便宜的にパラメータは考慮していません)。すると、fetchDataメソッドでいい感じに取得処理をしてくれて、通信が成功したか失敗したかの分岐に入って処理ができます。
なんだか便利に使えているようです。本来であればより複雑な処理をしますし、成功時と失敗時の処理もpresenterに渡す必要があるでしょう。
さいごに
なんとなくAPIクライアントというものがあり、一回ちゃんとした処理を書けばプロジェクト内で上手く使っていけるんだなと思ったと思います。少なくとも僕はそう思いました。より具体的な実装ができましたら記事に致します。
Name | Development Era | Features |
---|---|---|
Moya | 2015 | Swiftで書かれたネットワーク抽象化レイヤー。APIリクエストを管理しやすくする。 |
Alamofire | 2014 | HTTPネットワーキングを簡素化。Swiftで実装されており、モダンなAPIに対応。 |
Retrofit | 2012 | Android用。型安全なHTTPクライアント。アノテーションを使用してAPIインターフェースを定義。 |
Volley | 2013 | Android用。ネットワーキングおよび画像読み込みに適した軽量ライブラリ。 |
OkHttp | 2013 | 効率的なHTTP & HTTP/2クライアント。AndroidとJavaアプリ向け。 |
Discussion