GRIT UPGRIT UP

Blog

SwiftでAPIを使ってHTTPリクエストとMVVMによるアプリ作成

モバイルアプリSwiftiOS
SwiftでAPIを使ってHTTPリクエストとMVVMによるアプリ作成

SwiftでAPIを使ってHTTPリクエスト

初学者向けに3つを実例で解説します:

  • APIの基本的な使い方
  • HTTPリクエストの作成
  • MVVMモデルの実装

開発環境

  • Xcode: Version 13.3
  • SwiftUIを使用

作成物

コロナウイルスのデータを取得するアプリ「CovidSimple」を作成しました。ファイル構造は、モデル・ビュー・ビューモデルに分けて整理されています。

アーキテクチャ設計

MVVMモデルを採用しています。データフローは以下の通りです:

  1. MainViewがMainViewModelへ表示データのリクエスト
  2. MainViewModelがModel(TotalData)の作成
  3. MainViewModelがAPIServerへのリクエスト
  4. MainViewModelがMainViewへ応答

APIServer.swift

無料API「Covid-19-statistics」(RapidAPI提供)を使用しています。

import Foundation
 
final class APIService {
 
    static let shared = APIService()
    private let baseURLString = "https://covid-19-statistics.p.rapidapi.com"
    private let headers = [
        "X-RapidAPI-Host": "covid-19-statistics.p.rapidapi.com",
        "X-RapidAPI-Key": "[Your Key Here]"
    ]
 
    func fetchTotalData(completion: @escaping (Result<TotalData, Error>) -> Void) {
        // 1. API取得先URLの作成
        let totalURLString = baseURLString + "/reports/total"
        let url = URL(string: totalURLString)
 
        guard let url = url else {
            completion(.failure(CovidError.incorrectURL))
            return
        }
 
        // 2. URLリクエストの作成
        var request = URLRequest(
            url: url,
            cachePolicy: .useProtocolCachePolicy,
            timeoutInterval: 100.0
        )
        request.httpMethod = "GET"
        request.allHTTPHeaderFields = headers
 
        // 3. TASKの作成
        let session = URLSession.shared
        let dataTask = session.dataTask(with: request) { (data, response, error) in
            if error != nil {
                completion(.failure(CovidError.noDataReceived))
            } else {
                let decoder = JSONDecoder()
                do {
                    let totalDataObject = try decoder.decode(
                        TotalDataObject.self,
                        from: data!
                    )
                    completion(.success(totalDataObject.data))
                } catch let error {
                    completion(.failure(error))
                }
            }
        }
        // 4. TASKの実行
        dataTask.resume()
    }
}

APIServer.swiftの解説

3つの主要な処理があります:

  1. APIを取得するためのヘッダー作成 — HostはAPI取得先、Keyはアクセス鍵。基本的に非公開にすべきです。

  2. URLリクエストの作成

var request = URLRequest(
    url: url,
    cachePolicy: .useProtocolCachePolicy,
    timeoutInterval: 100.0
)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
  1. データを取得するタスクの作成と実行 — 通信成功時にJSONデータをデコードしてモデルに入れ、.successで返します。

Model

TotalDataObject構造体がメインモデルです。RapidAPIの応答構造に合わせています。

struct TotalDataObject: Codable {
    let data: TotalData
}
 
struct TotalData: Codable {
    let confirmed: Int
    let deaths: Int
    let confirmed_diff: Int
    let deaths_diff: Int
    let active: Int
    let fatality_rate: Double
 
    static let dummyData = TotalData(
        confirmed: 0,
        deaths: 0,
        confirmed_diff: 0,
        deaths_diff: 0,
        active: 0,
        fatality_rate: 0
    )
}

CodableプロトコルによってJSONデコードが可能になります。

View

MainView → TotalDataView → DataCardViewという階層構造になっています。

struct MainView: View {
    @StateObject private var viewModel = MainViewModel()
    let formatter = DateFormatter()
    let today: String
 
    init() {
        formatter.dateFormat = "y-MM-dd"
        self.today = formatter.string(from: Date())
    }
 
    var body: some View {
        VStack(alignment: .leading) {
            Text("世界の総数:(\(today))")
                .font(.title2.bold())
                .foregroundColor(.primary)
                .padding(10)
            TotalDataView(totalData: viewModel.totalData)
        }
    }
}

重要なのは@StateObject private var viewModel = MainViewModel()でViewModelをインスタンス化することです。

ViewModel

ModelとView、APIをつなぐ橋渡し役です。

final class MainViewModel: ObservableObject {
 
    @Published var totalData: TotalData = TotalData.dummyData
 
    init() {
        fetchTotalData()
    }
 
    func fetchTotalData() {
        APIService.shared.fetchTotalData { result in
            DispatchQueue.main.async {
                switch result {
                case .success(let totalData):
                    self.totalData = totalData
                case .failure(_):
                    self.alertItem = AlertContext.unableToFetchTotalStats
                }
            }
        }
    }
}

DispatchQueueを使う理由は、通信処理に時間がかかるため、アプリの裏側で実行する必要があるからです。

まとめ

本記事では初心者向けに以下3点を解説しました:

  • APIの基本的な使い方
  • HTTPリクエストの作成
  • MVVMモデルの実装

Contact

お問い合わせ

少しでも興味いただけましたらご連絡ください。

※ご連絡から1週間以内に返信いたします

CONTACT