[iOS] ベースクラスのextensionを、protocolで実装する話

2019/05/29

こんにちは。きんくまです。

Swiftのextensionは大変便利で、いろいろなクラスに対して拡張実装を作ることが可能です。

例えば

extension UIViewController {
    func showAlert(title: String?, message: String?, callback: @escaping () -> Void) {
        let alertController = UIAlertController(title: title,
                                                message: message,
                                                preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK",
                                     style: .default) { action in
            callback()
        }
        let cancelAction = UIAlertAction(title: "Cancel",
                                         style: .cancel,
                                         handler: nil)
        alertController.addAction(okAction)
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }
}

こんな感じに、UIViewControllerに対して、アラートを出すことができるメソッドを定義することができます。

ただ、個人的にはこういう全ての場面で共通で使いそうなクラス(この場合UIViewController)に対して拡張を書くというのが、気になるなーと思いました。どこが気になるかというと、

– クラスを拡張していることが、クラスの定義のところを見るだけだと気づかない
– 使う必要のないクラスにも適用されてしまう

という感じでしょうか。例えば、上のコードだと、アラートを出す必要のないUIViewControllerにも拡張されてしまいます。

解決方法

解決方法なのですが、Swiftにはprotocolが用意されているので、それでできそうです。

protocol AlertEnable where Self: UIViewController {
    func showAlert(title: String?, message: String?, callback: @escaping () -> Void)
}

extension AlertEnable {
    func showAlert(title: String?, message: String?, callback: @escaping () -> Void) {
        let alertController = UIAlertController(title: title,
                                                message: message,
                                                preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK",
                                     style: .default) { action in
                                        callback()
        }
        let cancelAction = UIAlertAction(title: "Cancel",
                                         style: .cancel,
                                         handler: nil)
        alertController.addAction(okAction)
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }
}

こんな感じに、protocolにwhere句で制限をつけて、protocolに対してデフォルトのextensionを実装します。

そうしたら、こんな感じに使えます。

class SampleViewController: UIViewController, AlertEnable {
    @IBAction func buttonTapped() {
        showAlert(title: "Hello", message: "world") {
            print("complete!")
        }
    }
}

こうすればアラートが必要なUIViewControllerに対してだけprotocolをつけてあげることが可能です。
また、SampleViewControllerは、AlertEnableプロトコルに対応しているので、メソッドが呼べるということもすぐにわかります。

感想

すんごく昔にPrototype.jsというJavaScriptのライブラリの先祖みたいなやつがありました。で、そいつはデフォルトの色んなObjectを拡張していました。そしたら、そういうのは良くないって流れになって、、。みたいな話がありまして。

影響範囲が狭いやつを拡張するのは良いのですが、ベースのクラスを拡張するのは気が引けるという個人的な好みの話でした。
まあ、自分が考えすぎ+考えが古いのかもしれないで、気に入らなかったらすみません、、。

2019/05/30追記

extensionで拡張する場合を考えてみました。

– 影響範囲が小さい拡張(ベースクラスじゃなくて、自作クラスのときとか)

– protocol, privateなどに対する拡張

extension SampleViewController: UITableViewDataSource {

}

とか

private extension SampleViewController {
    
}

みたいの

– ベースクラスに対する拡張で、ほとんど全ての場面で使うから問題ない場合

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

LINEスタンプ作りました!
毎日使える。とぼけたウサギ。LINEスタンプ販売中! 毎日使える。とぼけたウサギ

ページトップへ戻る