今回は、ポケモンのAPI通信の実装済みであるサンプルコードを用いて、初学者として勉強になったところをまとめてみる。
↑のGitHubは、ポケモンのAPIを使ったサンプルコード
↑がポケモンのAPIについて記載されている。ドキュメントあり。
JSON→構造体に変換に関するリファクタリングに有効
public static var decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
このコードは、JSONDecoder
オブジェクトを作成して、その keyDecodingStrategy
プロパティを .convertFromSnakeCase
に設定しています。
keyDecodingStrategy
プロパティは、JSON キー名が Swift の命名規則に準拠していない場合に、JSON キーを Swift の命名規則に変換するために使用されます。例えば、JSON キー名が snake_case
(アンダースコア区切り) の場合、keyDecodingStrategy
を .convertFromSnakeCase
に設定することで、Swift の命名規則である camelCase
(大文字で始まる) に変換することができます。
この設定を行うことで、JSON データを Swift の構造体やクラスにデコードする際に、より自然な命名規則を使うことができます。例えば、以下のような JSON データがあった場合、keyDecodingStrategy
を.convertFromSnakeCase
に設定することで、first_name
キーが firstName
としてデコードされます。
命名規則に従って、JSONが実装されている場合は、コード量を減らすことが可能である。
わざわざ 変換前のコードを書いることが多かったので、これは便利!っと感じました。
APIのパスパラメータの種類が多いときの実装例
public class PokemonAPI {
public let session: URLSession
public init(session: URLSession = URLSession.shared) {
self.session = session
}
public lazy private(set) var berryService = BerryService(session: session)
public lazy private(set) var contestService = ContestService(session: session)
public lazy private(set) var encounterService = EncounterService(session: session)
public lazy private(set) var evolutionService = EvolutionService(session: session)
public lazy private(set) var gameService = GameService(session: session)
public lazy private(set) var itemService = ItemService(session: session)
public lazy private(set) var locationService = LocationService(session: session)
public lazy private(set) var machineService = MachineService(session: session)
public lazy private(set) var moveService = MoveService(session: session)
public lazy private(set) var pokemonService = PokemonService(session: session)
public lazy private(set) var resourceService = ResourceService(session: session)
public lazy private(set) var utilityService = UtilityService(session: session)
}
PokemonAPI
の中に、様々なパスパラメータがある。それらを管理するために、PokemonAPI
クラスの中に、プロパティとして、保持しておく。参考になった。
API通信に必要なURLを管理する実装例
public struct PokemonService: PKMPokemonService {
public enum API: APICall {
case fetchAbilityList
case fetchAbilityByID(Int)
case fetchAbilityByName(String)
// 省略
case fetchTypeByName(String)
var path: String {
switch self {
case .fetchAbilityList:
return "/ability"
case .fetchAbilityByID(let id):
return "/ability/\(id)"
case .fetchAbilityByName(let name):
return "/ability/\(name)"
// 省略
case .fetchTypeByName(let name):
return "/type/\(name)"
}
}
}
public var session: URLSession
public var baseURL: String = "https://pokeapi.co/api/v2"
// 省略
}
PokemonService
構造体の中で、API
というenum
が定義されており、その中にpath
があり、各caseに対応するAPIエンドポイントのパスを返します。例えば、.fetchAbilityList
の場合は"/ability"
を、.fetchAbilityByID
の場合は"/ability/(id)"
を返します。また、各caseはそれぞれ必要な場合は、String
・Int
などの型を保持している。
また、baseURL
は、 "https://pokeapi.co/api/v2" という各パスパラメータの元となる部分であり、各APIのURLを完成させるために使用されます。API
列挙型のcase
のpath
プロパティをbaseURL
に追加することで完成します。
このように管理すればよいのか! と大変勉強になった。
列挙型のAPIに対して、プロトコルの作成して、共通化
protocol APICall {
var path: String { get }
}
extension APICall {
func createUrl(baseURL: String) -> URL? {
return URL(string: baseURL + path)
}
// 省略
}
先程のAPI実装例と話は関連しています。
上記のように、APICall
に、path
を設定することにより、各API実装において、path
の設定を忘れることはなくなり、APICall
の拡張での、func createUrl(baseURL: String) -> URL?
関数の中で、path
を用いることができ、各APIにおいて共通化してこのメソッドを使用することが可能となる。
上記によって、毎回createURL
メソッドを作成する必要がなくなる。
プロトコルによる抽象化の恩恵はこうゆうところで受けるんだなーと感じた。
次回は、エラーハンドリング周りのコードで、学習していく。
以上です。
他にも良い方法があれば、コメントいただけると大変うれしいです。
良かったと思ったら、いいねやTwitterのフォローよろしくお願いいたします!
https://sites.google.com/view/muranakar
個人でアプリを作成しているので、良かったら覗いてみてください!