LoginSignup
1
2

More than 1 year has passed since last update.

SwiftでMoyaを使う

Last updated at Posted at 2021-11-12

Moyaを使用して簡単な参考ソースコードを作成します。
今回は下記URLのAPIを例として使用しました。
https://www.themoviedb.org/
人気映画、映画の詳細、検索などが利用できるAPIです。

Moyaとは

Moyaはネットワーク抽象化レイヤーです。
MoyaはAlamofireを使用しており、ネットワークマネージャーを素早くセットアップできます。
必要な依存関係を追加することで、プロジェクトでMoyaを使用することができます。
プロジェクトをインストールする方法はいくつかあり、下記リンクにインストールに関する詳細が入手できます。
https://github.com/Moya/Moya#installation

API

Moyaをインストールしたら、まずAPI.swiftファイルを作成し、さまざまなリクエストをもつ列挙型(enum)を作成します。
API列挙型をMoyaのTargetTypeで拡張し、必要な変数に準拠する必要があります。
baseURL, path, sampleData, taskやheaderなどです。

import Moya

enum API {
    case popular
    case movie(movieId: String)
    case search(query: String)
}

extension API: TargetType {
    var baseURL: URL {
        guard let url = URL(string: "https://api.themoviedb.org/3/") else { fatalError() }
        return url
    }

    var path: String {
        switch self {
        case .popular:
            return "movie/popular"
        case .movie(let movieId):
            return "movie/\(movieId)"
        case .search:
            return "search/movie"
        }
    }

    var method: Method {
        return .get
    }

    var sampleData: Data {
        return Data()
    }

    var task: Task {
        switch self {
        case .popular, .movie:
            return .requestParameters(parameters: ["api_key": Constants.API.apiKey], encoding: URLEncoding.queryString)
        case .search(let query):
            return .requestParameters(parameters: ["query" : query, "api_key": Constants.API.apiKey], encoding: URLEncoding.queryString)
        }
    }

    var headers: [String : String]? {
        return nil
    }
}

baseURL: API URLの共通接頭辞。
path: baseURLの後につく。場合によっては動的な値を置く必要がある。例えば、映画の詳細を取得するのであれば、映画IDを

movie/{movieId}

のようなパスに配置する必要がある。
method: リクエストのHTTPメソッド。取得、投稿、削除、接続、ヘッド、オプション、パッチ、トレースが取得できる。
sampleData: テスト用の応答のスタブデータを作成することができます。テストを作成しない場合は、コードのように空のデータを返すことができます。
task: HTTPタスクを表します。いくつかのタスクがありますが、共通タスクについて説明します。クエリパラメータとしてapi_keyを渡す必要があるので、人気のある映画タイプにはリクエストパラメータを使用しました。

クエリパラメータやbodyを追加しないのであれば、requestPlainを使用します。
例として、クエリパラメータを追加するとき: ?api_key=xxx.
requestParametersを選ぶ必要があります。
また、エンコード可能なリクエストモデルを使用してbodyにrequestJSONEncodableを使用することができます。

ヘッダー: ヘッダーはリクエストで使用されます。リクエストのためにヘッダーが必要な場合、辞書にヘッダーを追加します。

ネットワークマネジャー

マッピングのための応答モデルを作る必要があります。
JSONレスポンスから応答モデルを生み出すために、下記URLを使いました。
https://app.quicktype.io
素早く応答モデルを生成するツールとしてこちらはおすすめです。
MovieResponse、MovieDetailResponse、SearchResponseのモデルを作成しました。
ファイル名はNetworkManager.swiftです。

import Moya

protocol Networkable {
    var provider: MoyaProvider<API> { get }

    func fetchPopularMovies(completion: @escaping (Result<MovieResponse, Error>) -> ())
    func fetchMovieDetail(movieId: String, completion: @escaping (Result<MovieDetailResponse, Error>) -> ())
    func fetchSearchResult(query: String, completion: @escaping (Result<SearchResponse, Error>) -> ())
}

class NetworkManager: Networkable {
    var provider = MoyaProvider<API>(plugins: [NetworkLoggerPlugin()])

    func fetchPopularMovies(completion: @escaping (Result<MovieResponse, Error>) -> ()) {
        request(target: .popular, completion: completion)
    }

    func fetchMovieDetail(movieId: String, completion: @escaping (Result<MovieDetailResponse, Error>) -> ()) {
        request(target: .movie(movieId: movieId), completion: completion)
    }

    func fetchSearchResult(query: String, completion: @escaping (Result<SearchResponse, Error>) -> ()) {
        request(target: .search(query: query), completion: completion)
    }
}

private extension NetworkManager {
    private func request<T: Decodable>(target: API, completion: @escaping (Result<T, Error>) -> ()) {
        provider.request(target) { result in
            switch result {
            case let .success(response):
                do {
                    let results = try JSONDecoder().decode(T.self, from: response.data)
                    completion(.success(results))
                } catch let error {
                    completion(.failure(error))
                }
            case let .failure(error):
                completion(.failure(error))
            }
        }
    }
}

フェッチメソッドとプロバイダーメソッドを含むプロトコルを作成します。
この例だと、メソッドは3つしかなく、APIファイルを備えたプロバイダーが機能します。
たくさんのリクエストを送信する必要がある場合、APIファイルは混乱してしまいます。
MovieAPI, LoginAPIのような異なるAPIファイルを作成でき、異なるプロバイダーを使うことができます。
したがって、それはもっと組織化されるでしょう。

MoyaはNetworkLoggerPluginとして呼ばれるプラグインがあり、要求と応答をコンソールに記録するために提供されます。
要求・応答の記録を見るためにこのプラグインを使用します。プラグインを使用してプロバイダーを初期化することができます。
フェッチメソッドの内部は応答モデルとクエリ以外は同じなので、汎用のリクエストメソッドを作成します。それはコードの重複を防ぎます。
Swift5に付属しているResult型を使用しました。
Result型は2つのケースを持つ列挙型として実装される: success, failure
下記のように自身のエラー型を作成でき、エラーとして使用することができます。

enum NetworkError: Error {
    case notFound
    case unexpectedError
}

呼び出し

マネージャーを使うためにネットワークマネージャーからインスタンスを取得します。
テスト容易性の分類するためにネットワークマネージャーを注入するのが良いでしょう。
したがって、クラスをテストする必要がある際は、モックネットワークマネージャーを簡単に作成し、クラスに注入することができます。

private let networkManager: NetworkManager
init(networkManager: NetworkManager = NetworkManager()) {          
    self.networkManager = networkManager
}

もしテストを書く必要がなければ、下記のようにインスタンスを取得することができます。

private let networkManager = NetworkManager()

サービスから要求データをフェッチする準備をします。

func loadMovieDetail(movieId: String) {
    networkManager.fetchMovieDetail(movieId: movieId, completion: { [weak self] result in
        guard let strongSelf = self else { return }
        switch result {
        case .success(let movieDetailResponse):
            strongSelf.movieDetail = movieDetailResponse
        case .failure(let error):
            print(error.localizedDescription)
        }
    })
}

下記のリンクにサンプルプロジェクトがあります。
https://github.com/omerkolkanat/moya-example

プロジェクトのネットワークモジュールをどう素早くビルドできるか説明しています。お役に立てれば幸いです。

参考文献

1
2
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
1
2