Swift Concurrency は魅力的です。
それを試しに使ってみるために、APIClientを作成しレスポンスをViewに表示してみます(SwiftUIを利用)。
GithubのREST APIを利用しています。
https://docs.github.com/ja/rest
最終的なコードはこちらにあります。
https://github.com/yyokii/APIClientUsingSwiftConcurrency
APIリクエスト/レスポンス
まず、APIリクエストする際の設定とレスポンスで受け取る型を定義します。
ここではSwift Concurrencyの機能は利用していません。
今回はGithubのユーザー情報取得のAPIを利用します。
レスポンスはこちらです。
public struct UserInformation: Codable {
public var login: String
public var id: Int
public var followers: Int
public var following: Int
}
APIリクエストのベースとなる実装をします。
public enum HTTPMethod: String {
case connect = "CONNECT"
case delete = "DELETE"
case get = "GET"
case head = "HEAD"
case options = "OPTIONS"
case patch = "PATCH"
case post = "POST"
case put = "PUT"
case trace = "TRACE"
}
public protocol GithubAPIBaseRequest {
associatedtype Response: Decodable
var baseURL: URL { get }
var method: HTTPMethod { get }
var path: String { get }
var body: String { get }
var queryItems: [URLQueryItem] { get }
func buildURLRequest() -> URLRequest
}
extension GithubAPIBaseRequest {
public func buildURLRequest() -> URLRequest {
let url = baseURL.appendingPathComponent(path)
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)
switch method {
case .get:
components?.queryItems = queryItems
default:
fatalError()
}
var urlRequest = URLRequest(url: url)
urlRequest.url = components?.url
urlRequest.httpMethod = method.rawValue
return urlRequest
}
}
ユーザー情報を取得するAPIリクエストの型を作成します。
let baseURLString: String = "https://api.github.com"
/// Get a user
public struct UserInformationRequest: GithubAPIBaseRequest {
public typealias Response = UserInformation
public var baseURL: URL = URL(string: baseURLString)!
public var path: String = "users"
public var method: HTTPMethod = .get
public var body: String = ""
public var queryItems: [URLQueryItem] = []
public init(userName: String) {
path += "/\(userName)"
}
}
APIClient
APIClientを実装します。
APIClientのエラーを定義します。
enum GithubAPIClientError: Error {
case connectionError(Data)
case apiError
}
やっとAPIClientです。
send
メソッドにてasync
キーワードが登場しています。
public struct GithubAPIClient {
private let session: URLSession = {
let config = URLSessionConfiguration.default
return URLSession(configuration: config)
}()
private let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
public init() {}
public func send<Request: GithubAPIBaseRequest>(request: Request) async throws -> Request.Response {
let result = try await session.data(for: request.buildURLRequest())
try validate(data: result.0, response: result.1)
return try decoder.decode(Request.Response.self, from: result.0)
}
func validate(data: Data, response: URLResponse) throws {
guard let code = (response as? HTTPURLResponse)?.statusCode else {
throw GithubAPIClientError.connectionError(data)
}
guard (200..<300).contains(code) else {
throw GithubAPIClientError.apiError
}
}
}
ViewModel
先ほど作ったAPIClientを利用してみます。
ポイントは以下です。
-
@MainActor
により、変更通知がメインスレッド上で実行されます。 - APIClientの
send
メソッドを利用し、await
キーワードを利用してデータを取得しています。
@MainActor
final class HomeViewModel: ObservableObject {
@Published var id: Int = -1
@Published var login: String = ""
@Published var followers: Int = 0
@Published var following: Int = 0
let client = GithubAPIClient()
init() {}
func fetchChrisInfo() async {
let req = UserInformationRequest(userName: "defunkt")
do {
let data: UserInformation = try await client.send(request: req)
self.id = data.id
self.login = data.login
self.followers = data.followers
self.following = data.following
} catch {
print(error)
}
}
}
描画
最後に描画です。
OnAppear
にて、データ取得処理を呼び出しています。
Task.init
を利用し、非同期関数を呼び出すことができます。
public struct HomeView: View {
@StateObject private var vm: HomeViewModel = HomeViewModel()
public init() {}
public var body: some View {
VStack(spacing: 20) {
Text("ID: \(vm.id)")
Text("Login: \(vm.login)")
Text("Followers: \(vm.followers)")
Text("Following: \(vm.following)")
}
.onAppear {
Task {
await vm.fetchChrisInfo()
}
}
}
}
Conclusion
コードはこちらにあります。
https://github.com/yyokii/APIClientUsingSwiftConcurrency
いかがでしたでしょうか?
Swift Concurrencyに触れるきっかけになれば幸いです。