問題点の整理
try! Swiftに参加した時の話です。登壇者の Michele Titolo さんの話に気になる話題が出ていました。
あるプロトコルを持ったサブクラス、を定義した時の話です。定義自体は簡単です。Michele さんの例は以下の通りです。
protocol Themeable {}
class ListViewController: UIViewController, Themeable {}
問題は、さて、これを変数などに持つ時はどうすれば良いですか?
var themedViewController: // UIViewController, Themeable ????
ちなみに Objective-C ならこんな風に書くことができます。
@protocol Themeable <NSObject>
@end
@interface ListViewController : UIViewController <Themeable> {
}
@end
UIViewController<Themeable> *themedViewController;
つまり、Themeable
な UIViewController
はそのどちらかの型として変数に置かれ、利用時に必要に応じてキャストして使われる事が強いられる事になります。例えば、UIViewController
の変数とすれば、Themeable
なメソッドにアクセスする時はその度事にキャストしてあげなくてはなりません。
Michele さんは2つの方法を提案しています。
方法1
public protocol ViewControllerProtocol {
var view: UIView! { get }
var storyboard: UIStoryboard? { get }
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
func viewDidLoad()
// etc
}
protocol ThemeableViewController: Themeable, ViewControllerProtocol {}
var themedViewController: ThemeableViewController
方法2
var themed: Themeable {
get {
return self.viewController as! Themeable
}
set(newThemeable) {
if let themedViewController = newThemeable as? UIViewController{
viewController = themedViewController
}
}
}
var viewController: UIViewController
init<T where T: Themeable, T: UIViewController>(viewController: T) {
self.viewController = viewController
}
説明は割愛させていただきますが、本人も Hack-y と言っている通り、ストレートフォーワードとはいきません。
第3の方法
私もこの問題の解決方法をずっと考えていました。嘘です。時折思い出しながら、なんとかならないものかと思いなやんでおりました。そんな時たまたま読んでいた Qiita の記事を読んでいていて「ピン!」としました。その記事はこちらです。
UIStoryboardからViewControllerをロードするときにenumとprotocolとextensionを使ってカッコよくやる方法
その中にこんな表現がありました。
extension StoryboardIdentifiable where Self: UIViewController {}
こ!これだ!と思い、こんな風に書いてみました。
protocol Themeable {
var viewController: UIViewController { get }
}
extension Themeable where Self: UIViewController {
var viewController: UIViewController { return self }
}
これで、Themeable
は UIViewController
であるという縛りを入れる事ができます。Themeable
な UIViewController
とそのインスタンスを生成してみましょう。
class ThemeableViewController: UIViewController, Themeable {}
let themeable: Themeable = ThemeableViewController()
themeableViewController 自体が viewController であるという訳ではありませんので、viewController のメソッドにアクセスする時は以下のようにします。
themeable.viewController.view.backgroundColor = UIColor.clearColor()
不完全と言われればそれまでですが、コンパイル時に Themeable
は UIViewController
がある事が保証されています。例えば、UIViewController
をベースとしないクラスを Themeable
に適合させても、コンパイル時にエラーとなります。
class ThemeableObject: NSObject, Themeable {} // error
すっきり爽快という訳ではありませんが、ストレートフォーワードに思えます。
そもそも
そもそも、なんで Themeable
と UIViewController
と分離したのですがという人もいるかもしれません。確かに ThemeableViewController
なんてクラスを設けて、すべての具象クラスはこいつのサブクラスにすればいいじゃないかという考え方もあります。ただし、UITableViewController
や他のサブクラスをどう Themeable
で扱えばいいかと考えると、Themeable
と UIViewController
を分離するメリットもご理解いただけるのではないかと思います。
終わりに
おそらく、もっと知恵を集めれば、もう少し良くできそうな予感がしますが、今日はここまでとします。
Michele さんの登壇話は以下から参照いただけます。