13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MyProtocol に適合した MyClass のインスタンスを変数に持つ方法

Last updated at Posted at 2016-03-19

問題点の整理

try! Swiftに参加した時の話です。登壇者の Michele Titolo さんの話に気になる話題が出ていました。

あるプロトコルを持ったサブクラス、を定義した時の話です。定義自体は簡単です。Michele さんの例は以下の通りです。

.swift
protocol Themeable {}
class ListViewController: UIViewController, Themeable {}

問題は、さて、これを変数などに持つ時はどうすれば良いですか?

.swift
var themedViewController: // UIViewController, Themeable ????

ちなみに Objective-C ならこんな風に書くことができます。

.objc
@protocol Themeable <NSObject>
@end

@interface ListViewController : UIViewController <Themeable> {
}
@end
.objc
UIViewController<Themeable> *themedViewController;

つまり、ThemeableUIViewController はそのどちらかの型として変数に置かれ、利用時に必要に応じてキャストして使われる事が強いられる事になります。例えば、UIViewController の変数とすれば、Themeable なメソッドにアクセスする時はその度事にキャストしてあげなくてはなりません。

Michele さんは2つの方法を提案しています。

方法1

.swift
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

.swift
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を使ってカッコよくやる方法

その中にこんな表現がありました。

.swift
extension StoryboardIdentifiable where Self: UIViewController {}

こ!これだ!と思い、こんな風に書いてみました。

.swift
protocol Themeable {
	var viewController: UIViewController { get }
}

extension Themeable where Self: UIViewController {
	var viewController: UIViewController { return self }
}

これで、ThemeableUIViewController であるという縛りを入れる事ができます。ThemeableUIViewController とそのインスタンスを生成してみましょう。

.swift
class ThemeableViewController: UIViewController, Themeable {}
let themeable: Themeable = ThemeableViewController()

themeableViewController 自体が viewController であるという訳ではありませんので、viewController のメソッドにアクセスする時は以下のようにします。

themeable.viewController.view.backgroundColor = UIColor.clearColor()

不完全と言われればそれまでですが、コンパイル時に ThemeableUIViewController がある事が保証されています。例えば、UIViewController をベースとしないクラスを Themeable に適合させても、コンパイル時にエラーとなります。

.swift
class ThemeableObject: NSObject, Themeable {} // error

すっきり爽快という訳ではありませんが、ストレートフォーワードに思えます。

そもそも

そもそも、なんで ThemeableUIViewController と分離したのですがという人もいるかもしれません。確かに ThemeableViewController なんてクラスを設けて、すべての具象クラスはこいつのサブクラスにすればいいじゃないかという考え方もあります。ただし、UITableViewController や他のサブクラスをどう Themeable で扱えばいいかと考えると、ThemeableUIViewController を分離するメリットもご理解いただけるのではないかと思います。

終わりに

おそらく、もっと知恵を集めれば、もう少し良くできそうな予感がしますが、今日はここまでとします。

Michele さんの登壇話は以下から参照いただけます。

13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?