【Swift】ポケモンのAPI通信のサンプルコードから、API通信処理を学習する - Decoder・URLSessionなど -

今回は、ポケモンのAPI通信の実装済みであるサンプルコードを用いて、初学者として勉強になったところをまとめてみる。


https://github.com/kinkofer/PokemonAPI

↑のGitHubは、ポケモンのAPIを使ったサンプルコード

https://pokeapi.co/

↑がポケモンのAPIについて記載されている。ドキュメントあり。


JSON→構造体に変換に関するリファクタリングに有効
-
```swift:
 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のパスパラメータの種類が多いときの実装例
-
```swift: PokemonAPI.swift
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を管理する実装例
-
```swift:Pokemon.swift
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に対して、プロトコルの作成して、共通化　
-
```swift: APICall.swift
 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
個人でアプリを作成しているので、良かったら覗いてみてください！