Help us understand the problem. What is going on with this article?

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

freddi_
プログラミングを捨てて、悟りを開くのが夢です
https://freddi.dev
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away