Extensionをモダンでオシャレにしたい
この記事の結論を先に書いておくと、
UIColor.yamabuki
のように書いているExtensionを以下のようにオシャレに書くことができる、ということを解説します。
UIColor.ex.yamabuki
長くなってんじゃねーか。
と、思うかもしれませんが、こうすることによって型に含まれる標準的な機能と拡張機能を明確に区別することができます。
ちょっと便利。ちょっと。
Extensionをどうやって定義するか問題
なぜこのような話をするのかというと、Extensionをどうやって定義するかが問題になったりするからです。小さな問題ではあるんですけど。
Extensionは欲しいところに欲しいものを作ることができるので、結構使う機会が多いんじゃないかと思いますが、どこにどうやって定義するかまで考えたいところです。
局所的に使うものであればprivate
なりfileprivate
なりのアクセスコントロールを使って公開範囲を制限したりするかと思います。
逆にプロジェクト全体で使うようなものに関しては、public
にしてClass+Extension.swift
みたいなファイルを作成して定義するかと思います。
これらの方法でも問題ないんですが冒頭に述べたとおり、
UIColor.yamabuki
のような書き方だとデフォルトで定義されているものなのか、拡張定義されているものなのか分かりません。
Extensionであることを明確にする
冒頭で書いたような、
UIColor.ex.yamabuki
みたいな定義をRxSwiftが行っています。
RxのようなReactive Programmingを行うための拡張の場合、このように明示的に書くことは有効なのではないかと思います。
これから実装方法の解説のためにRxSwiftのコードが出てきますが、本題とはあんまり関係ないのであまり深く考えないでサラッと読み流してください(RxSwiftやRxCocoaの違いなどの説明は省略します)。
コードの解説
RxSwiftの2系では、ボタンがタップされたときにtap
と出力するには以下のようなコードを書いていました。
button.rx_tap
.subscribeNext { _ in
print("tap")
}
.addDisposable(disposeBag)
これがRxSwift3系では以下のように変更されています。
button.rx.tap
.bindNext { _ in
print("tap")
}
.addDisposable(disposeBag)
subscribeNext
がbindNext
に変わっていますがほぼ同じことをしているので、そこは一旦置いておきます。
button.rx_tap
→button.rx.tap
と変わっていることに着目してみましょう。
Rxでの内部実装
2系ではこのように実装されていました。
extension UIButton {
/**
Reactive wrapper for `TouchUpInside` control event.
*/
public var rx_tap: ControlEvent<Void> {
return rx_controlEvent(.TouchUpInside)
}
}
UIButton
に限らず、Rx拡張は単純にrx_*
というプリフィックスをつけてExtensionを定義していました(全部に目は通していませんがたぶんそうです)。
なんかオシャレじゃない感じ。
それが、3系では以下のようにジェネリクスを使ってモダンでオシャレな実装に変更されました。
extension Reactive where Base : UIButton {
/**
Reactive wrapper for `TouchUpInside` control event.
*/
public var tap: RxCocoa.ControlEvent<Swift.Void> { get }
}
以上を踏まえてもう一度見てみましょう。
button.rx_tap
.subscribeNext { _ in
print("tap")
}
.addDisposable(disposeBag)
button.rx.tap
.bindNext { _ in
print("tap")
}
.addDisposable(disposeBag)
このあたりの変更内容については https://github.com/ReactiveX/RxSwift/blob/db75d01f3179eb6e3e534ed29815ac7920b5674b/CHANGELOG.md#features で解説されています。
Swiftのcollection
で定義されているlazy
にインスパイアされて作られたようです。
実装方法
ex
というプロパティを経由してExtension定義したものにアクセスしたい場合は以下のようなStructとProtocolを定義します。
struct Extension<Base> {
let base: Base
init (_ base: Base) {
self.base = base
}
}
protocol ExtensionCompatible {
associatedtype Compatible
static var ex: Extension<Compatible>.Type { get }
var ex: Extension<Compatible> { get }
}
extension ExtensionCompatible {
static var ex: Extension<Self>.Type {
return Extension<Self>.self
}
var ex: Extension<Self> {
return Extension(self)
}
}
Extensionであることを分かりやすくするために、Extension<Base>
としてみました。
このExtension<Base>
でExtensionCompatible
に対応した型をラップ、そしてExtensionCompatible
のデフォルト実装で.ex
というプロパティを定義しています。
今回はExtension<Base>
でしたが、RxSwiftはReactive<Base>
としています。必要であれば用途ごとにラッパーを作ってみるのも良さそうです。
準備ができたので冒頭で見たコードを実装しましょう。
UIColor.ex.yamabuki
これは以下のように実装します。
extension UIColor: ExtensionCompatible {}
extension Extension where Base: UIColor {
static var yamabuki: UIColor {
return UIColor(red:0.97, green:0.71, blue:0.00, alpha:1.0)
}
}
これでOK。Extension<Base>
がUIColor
だった場合のデフォルト実装として定義しています。
UIColor
に対する拡張ではなく、Extension<Base>
の制約付きの拡張ですね。
まとめ
通常のExtensionでここまでするかというと、多分しないと思うのですが、RxSwiftみたいに通常のプロパティとObservableなプロパティを分けたいような場合には定義がやりやすくなる気がします。
今回取り上げたような単純なExtensionの定義でも明確に分けたい場合は結構役に立つのではないかと思います。