読者です 読者をやめる 読者になる 読者になる

Swiftでジェネリックなメソッドの特殊化を行う方法

今日色々とやり取りをしながら、Swiftでのジェネリックメソッドについてある知見を得たのでまとめておきます。

以下のようなSwiftの型・ジェネリックメソッドがある時、このままでは型指定(特殊化)が面倒になってしまいます。

class Hoge {
    class func genericMethod<T: Request>(callback: T -> Bool) {
        ...
    }
}

// 呼ぶ時
Hoge.genericMethod { (x: SomeRequest) in true }

上記のような場合、型情報のヒントが与えられないのでクロージャでパラメータの型を明示しないといけません。引数にTインスタンス・値を渡す場合は型推論が効くし、以下のように型自体がジェネリックな場合は初期化時に明示的に特殊化が出来るのですが。。

class Generic<T> {}

let generic = Generic<String>()

このようなケースでは、メソッドのパラメータとして型自体を渡させるようにするとそれが型のヒントとなりクロージャで型指定を行う必要がなくなります。

class Hoge {
    class func genericMethod<T: Request>(type: T.Type, callback: T -> Bool) {
        ...
    }
}

// 呼ぶ時
Hoge.genericMethod(SomeRequest.self) { x in true }

振り返ってみればObjective-CではこのようにClassをパラメータで渡すことも多かった気がしますね。

このパターンを使うと、例えばUITableViewCellのdequeueが以下のように出来、キャストが不要になります。

extension UITableView {
    func registerNibForClass<T: UITableViewCell>(type: T.Type) -> UINib {
        let nib = UINib(nibName: type.nibName(), bundle: nil)
        registerNib(nib, forCellReuseIdentifier: type.reuseIdentifier())
        return nib
    }

    func dequeueCell<T: UITableViewCell>(type: T.Type, forIndexPath indexPath: NSIndexPath) -> T {
        return dequeueReusableCellWithIdentifier(type.reuseIdentifier(), forIndexPath: indexPath) as T
    }
}

extension UITableViewCell {
    class func simpleClassName() -> String {
        return NSStringFromClass(self).componentsSeparatedByString(".").last!
    }

    class func nibName() -> String { return simpleClassName() }

    class func reuseIdentifier() -> String { return simpleClassName() }
}

let cell = tableView.dequeueCell(HogeCell.self, forIndexPath: indexPath)

Javaとかでは出来たような気がしますが Hoge.genericMethod<SomeRequest> { x in true } みたいにメソッドコール時に明示的に特殊化が出来ればいいんですけどね。