Swiftで型消去を利用したAPIClientDelegateの実装の話

# APIClientの実装
APIClientの実装をしたくて、とりあえず適当に簡単なprotocolを実装してみた。

```swift
protocol APIClient {
    associatedtype GetDataType
    func get()
}
```

`get()`の中で、`URLSessionDataTask`を使って非同期でGETする前提の`APIClient`を考えた。
このprotocolを使って、https://google.com をただ単にGETするだけの`APIClient`を作ってみた。

```swift
class GetGoogleDataClient: APIClient {
    typealias GetDataType = String
    
    private let urlString: String = "https://google.com/"
    
    func get() {
        let dataTask = URLSession.shared.dataTask(with: URL(string: urlString)!) { [weak self] data, error, response in
            if let data = data, let stringData = String(data: data, encoding: .utf8) {
                // dataにGETしたデータが入る
            }
        }
        dataTask.resume()   
    }
}
```

# APIClientDelegateの実装？

`dataTask`でGETされたデータを、クライアントを使っているclassに通知したい、と思ったので簡単にprotocolである`APIClientDelegate`を実装してみる。

```swift
protocol APIClientDelegate: class {
    associatedtype GetDataType
    func getDataIsFinished(data: GetDataType)
}
```

この時点での理想形は以下。

```swift
class GetGoogleDataClient: APIClient {
    typealias GetDataType = String

    weak var delegate: APIClientDelegate? // NG
    private let urlString: String = "https://google.com/"
    
    func get() {
        let dataTask = URLSession.shared.dataTask(with: URL(string: urlString)!) { [weak self] data, error, response in
            if let data = data, let stringData = String(data: data, encoding: .utf8) {
                self?.delegate.getDataIsFinished(data: data)
            }
        }
        dataTask.resume()   
    }
}
```

しかし、このままでは`APIClientDelegate`型を利用した変数は宣言できない。

```swift  
class GetGoogleDataClient: APIClient {
   ...
   weak var delegate: APIClientDelegate? // NG
   ...
}
```

変数として宣言するときに`associatedtype`である`GetDataType`が抽象的なままであるからだ。(以下エラー文)

```
Protocol 'APIClientDelegate' can only be used as a generic constraint because it has Self or associated type requirements
```

# 型消去によるAPIClientDelegateの問題点の解決
今回は、`APIClientDelegate`の`GetDataType`による問題を解決したいので、早速型消去を利用して問題点の解決を試みた。
少々古いドキュメントであるが、https://qiita.com/S_Shimotori/items/458a50d8f8e54e39de59 を見れば型消去の概要はだいたい理解できる。

実装した`AnyAPIClientDelegate`はこちら。
ジェネリクス`GetType`は`APIClientDelegate`中の`GetDataType`にあたる、と考えれば良い。

```swift
class AnyAPIClientDelegate<GetType>: APIClientDelegate {
    private let _getDataIsFinished: ((GetType) -> Void)
    
    required init<U: APIClientDelegate>(_ delegate: U) where U.GetDataType == GetType {
        self._getDataIsFinished = delegate.getDataIsFinished
    }
    
    func getDataIsFinished(data: GetType) {
        _getDataIsFinished(data)
    }
}
```

実際に`AnyAPIClientDelegate`型のメンバ変数を宣言すると、エラーが出なくなる。

`GetGoogleDataClient`の完成形は以下。

```swift
class GetGoogleDataClient: APIClient {
    typealias GetDataType = String

    weak var delegate: AnyAPIClientDelegate<GetDataType>? // OK
    private let urlString: String = "https://google.com/"
    
    func get() {
        let dataTask = URLSession.shared.dataTask(with: URL(string: urlString)!) { [weak self] data, error, response in
            if let data = data, let stringData = String(data: data, encoding: .utf8) {
                self?.delegate.getDataIsFinished(data: data)
            }
        }
        dataTask.resume()   
    }
}
```

# AnyAPIClientDelegateを利用してみる
では、`GetGoogleDataClient`を利用する`Hoge`クラスを実装する。

```swift
class Hoge {
    let dataClient = GetGoogleDataClient()
    
    init() {
    }
    
    func showSomeData() {
        dataClient.get()
    }
}
```
`dataClient`が受け取ったデータを通知できるように、`Hoge`を`APIClientDelegate`に準拠させる。
今回はただ単に受け取ったデータをprintしてみる。

```swift
extension Hoge: APIClientDelegate {
    func getDataIsFinished(data: String) {
        print(data)
    }
}
```
その後、`dataClient`のdelegateとして、自身を`AnyAPIClientDelegate`で初期化して渡す。

```swift
class Hoge {
   ...
   init() {
        dataClient.delegate = .init(self)
   }
   ...
}
```

後は以下のように利用すれば、データがprintされる。

```swift
let hoge = Hoge()
hoge.showSomeData() // データがprintされる
```

# まとめ
 - 型消去による、protocolの不自由から解き放たれた`AnyAPIClientDelegate`を実装した。
 - 正直Rxとか使ったほうが楽かもしれないが、何かしらのこだわりがあるときは是非参考に。

今回利用したソースコードは以下で公開している、
https://gist.github.com/freddi-kit/47746f60259c16e134413d6aa15b6ecc