soranoba
soranoba Author of soranoba.net
programming

associatedtypeを持つprotocolをクラスのメンバ変数として使う方法

Swiftのprotocolには, protocol版Genericsとでも言うべきassociatedtypeがあります.

public protocol Loader {
    associatedtype ItemType

    func load() -> [ItemType]
}

これを安直に使おうとするとコンパイルエラーになり, どうしたものかと悩まされることになります.

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

クラスのメンバ変数に利用する方法

型変数を消すType erasure(型消去)が使われることが多いですが, クラスのメンバ変数に持つだけであればGenericsにすることで対応できます.

class ViewController<LoaderType: Loader>: UIViewController where LoaderType.ItemType == Movie
{
    private var loader: LoaderType

    // MARK: - Lifecycle

    init(loader: LoaderType) {
        self.loader = loader
        super.init(nibName: "ViewController", bundle: Bundle(for: type(of: self)))
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

このようにすると, ItemTypeMovieLoaderであることを指定することができます.

型推論に関するバグへの対処方法

注意点として, Swift 5.6現在, このクラスのメソッド内で以下のコードを書くとコンパイルエラーになります.

    @IBAction private func didTapButton(_ sender: UIButton) {
        // Failed: Cannot convert value of type 'MovieLoader2' to expected argument type 'LoaderType'
        let vc = ViewController(loader: MovieLoader2())
        present(vc, animated: true, completion: nil)
    }

これを回避するには, 型推論ではなく明示的に型を指定する必用があります.

    @IBAction private func didTapButton(_ sender: UIButton) {
        let vc = ViewController<MovieLoader2>(loader: MovieLoader2())
        present(vc, animated: true, completion: nil)
    }

このバグはapple/swift#58413で報告しているので, 最新の状況はこちらを参照してください.

(Updated: )

comments powered by Disqus