Posted at

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

More than 1 year has passed since last update.


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の問題点の解決

今回は、APIClientDelegateGetDataTypeによる問題を解決したいので、早速型消去を利用して問題点の解決を試みた。

少々古いドキュメントであるが、https://qiita.com/S_Shimotori/items/458a50d8f8e54e39de59 を見れば型消去の概要はだいたい理解できる。

実装したAnyAPIClientDelegateはこちら。

ジェネリクスGetTypeAPIClientDelegate中の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が受け取ったデータを通知できるように、HogeAPIClientDelegateに準拠させる。

今回はただ単に受け取ったデータを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