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
プロジェクトのネットワークモジュールをどう素早くビルドできるか説明しています。お役に立てれば幸いです。
参考文献