Swift

SwiftでExtensionを実装する際に hogehoge.ex.foo() のようにプロパティを噛ましたい

Extensionを実装するときの悩み

SwiftのExtensionはクラスへの機能追加を,クラスを継承して新しいクラスを作ることなく,既存クラスにメソッドを追加できるので便利です.私も日々利用しています.しかしながら,考え付けたメソッドの名称がそのクラス(や親クラス)が持つプロパティやメソッドと同名だったり,プロジェクトにいつかサードライブラリを入れてるとExtensionで追加されたメソッドがどのライブラリから生成されたのか一見分からない,そもそも元からあるメソッドなのか追加されたメソッドなのかぱっと見で分からないと,名前で悩むことがあります.

それらへの対処方法

サードライブラリを見てると hogehoge.ex.foo() のように1つプロパティを噛ませて,Extensionを実装しているのを見かけます.これなら,名前が衝突することも避けれるし,メソッドの提供元が一目瞭然.ということで,このやり方でExtensionを実装しました.

Extension用のプロパティを生やす

まず,Extension用のプロパティ(変数)となるクラス MyExtension を作成し,それを持つProtocol MyExtensionProtocol を実装します(クラス名などは適当に決めています).

// 追加されるプロパティにあたるクラス
public class MyExtension<T> {
    // 対象となるインスタンスはこのbaseでアクセスする
    let base: T
    init (_ base: T) {
        self.base = base
    }
}

// protocolの宣言
public protocol MyExtensionProtocol {
    associatedtype T
    var ex: MyExtension<T> { get }
}

// protocolの実装
public extension MyExtensionProtocol {
    public var ex: MyExtension<Self> {
        return MyExtension(self)
    }
}

次に,作成したProtocolを用いて,Extension用のプロパティを既存クラスに生成して,そのプロパティ内でExtensionのメソッドを書いています.

// 試しに UIView に適当なメソッドを追加する
extension UIView : MyExtensionProtocol{}

// hogehoge.ex.foo() でメソッドを追加できる
public extension MyExtension where T: UIView {

    // 例えば,view の frame をただただ標準出力に出す    
    public func foo() {
        // extensionを実装したい元のクラスはMyExtensionのbaseになる
        let v: UIView = self.base
        print("v.frame = \(v.frame)")
    }
}

// Extension で追加したメソッドを呼ぶ
func doExample(view: UIView){
    view.ex.foo()
}

ここで String など構造体型はProtocolの適用が上記のような : ではなく = にします.

extension String : MyExtensionProtocol{}
public extension MyExtension where T = String {
    // 実装は同じ
}

まとめ

これでExtensionで追加したメソッドを既存メソッドと区別することができます.既存クラスに恰も最初からあるようにメソッドを追加できるExtensionの利点を潰すことになりますが,規模が大きくなると区別できた方が良いと思います.