iOS
Swift

一部のViewControllerだけに機能を追加するのにprotocol extensionが便利だった話

More than 1 year has passed since last update.


初めに

最初のリリースから数年程度が経過し細々とした機能追加を行っているようなプロジェクトで、

全体で50個くらいあるViewControllerのうち3つにだけ全く同じ機能を追加したい。

ただし、その3つのViewControllerは別々のスーパークラスを継承している。

という状況に陥った。

こんなときにSwiftのprotocol extensionがとても便利だった。


機能追加前の状態

こんな感じでViewControllerがたくさんあり、

このうちViewController2ViewController4 だけに新たな機能を追加したい。

また、その追加する機能の中でUIViewControllerで定義されたpropertyやmethodにアクセスしたい。


import UIKit

class ViewController1: UIViewController {
}

class ViewController2: UIViewController {
}

class ViewController3: UIViewController {
}

class ViewController4: UIViewController {
}

class ViewController5: UIViewController {
}


考えたこと

今更共通のスーパークラスを作ってそれを継承させるなんてありえない。is-a 的な扱いをしたい部分でもないし。

UIViewController のextensionを作ってもいいのだが、そうすると関係ないViewControllerにまで不要な実装が混じってしまって気持ち悪い。

それ用のclassを作って処理を委譲するのもいいがもう少しスマートなやりかたはないものか。


やったこと

まず、以下のように追加したい機能を持つprotocolを作成する。

protocol NewFeature {

func newFunction()
var isFantastic: Bool { get }
}

次に、このprotocolのデフォルト実装をもつextensionを作成する。

このとき、実装されるclassがUIViewControllerに限定されるようにする。

extension NewFeature where Self: UIViewController {

func newFunction() {
// UIViewControllerのpropertyやmethodが使える
guard 0 < view.subviews.count else {
print("Viewが空だ")
return
}

if isFantastic {
print("最高だ")
} else {
print("最高でない")
}
}
}

最後に、機能追加したいViewControllerだけこのprotocolを実装する。

ここではViewController2ViewController4 の2つ。

import UIKit

// newFunction の中で使っているpropertyの実装は各ViewControllerごとに独自で定義できる。

class ViewController1: UIViewController {
}

class ViewController2: UIViewController, NewFeature {
var isFantastic: Bool {
return parent != nil
}
}

class ViewController3: UIViewController {
}

class ViewController4: UIViewController, NewFeature {
var isFantastic: Bool {
return childViewControllers.count == 0
}
}

class ViewController5: UIViewController {
}

これで、必要なViewControllerだけがnewFunction を見ることが出来る状態になった。

しかもextensionを使う場合と異なり、それぞれのViewControllerごとに振る舞いを変えたい部分だけデフォルト実装を持たせないようにすることで実装する側で柔軟に振る舞いを変えることが出来る。


終わりに

Swiftではこれが一番スマートなやり方な気がする。

もっと良さそうなやり方があったらご教示ください。