APIClientの実装
APIClientの実装をしたくて、とりあえず適当に簡単なprotocolを実装してみた。
protocol APIClient {
associatedtype GetDataType
func get()
}
get()
の中で、URLSessionDataTask
を使って非同期でGETする前提のAPIClient
を考えた。
このprotocolを使って、https://google.com をただ単にGETするだけのAPIClient
を作ってみた。
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
を実装してみる。
protocol APIClientDelegate: class {
associatedtype GetDataType
func getDataIsFinished(data: GetDataType)
}
この時点での理想形は以下。
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
型を利用した変数は宣言できない。
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
にあたる、と考えれば良い。
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
の完成形は以下。
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
クラスを実装する。
class Hoge {
let dataClient = GetGoogleDataClient()
init() {
}
func showSomeData() {
dataClient.get()
}
}
dataClient
が受け取ったデータを通知できるように、Hoge
をAPIClientDelegate
に準拠させる。
今回はただ単に受け取ったデータをprintしてみる。
extension Hoge: APIClientDelegate {
func getDataIsFinished(data: String) {
print(data)
}
}
その後、dataClient
のdelegateとして、自身をAnyAPIClientDelegate
で初期化して渡す。
class Hoge {
...
init() {
dataClient.delegate = .init(self)
}
...
}
後は以下のように利用すれば、データがprintされる。
let hoge = Hoge()
hoge.showSomeData() // データがprintされる
まとめ
- 型消去による、protocolの不自由から解き放たれた
AnyAPIClientDelegate
を実装した。 - 正直Rxとか使ったほうが楽かもしれないが、何かしらのこだわりがあるときは是非参考に。
今回利用したソースコードは以下で公開している、
https://gist.github.com/freddi-kit/47746f60259c16e134413d6aa15b6ecc