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