はじめに
Swiftでオブジェクトのインスタンス化をしつつpropertyに値を代入する場合は、以下のような実装になるかと思います。
let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textColor = .red
label.text = "Hello, World!!"
return label
}()
上記と同じ状態のインスタンスを、メソッドチェーンで実現する方法を紹介しようと思います。
DuctTapeを利用する
DuctTapeというOSSを利用することで、先程の実装を以下のような実装にできます。
let label = UILabel().ductTape
.numberOfLines(0)
.textColor(.red)
.text("Hello, World!!")
.build()
NSObjectを継承したオブジェクトのインスタンスの.ductTape
にアクセスすると、Builderが返ってきます。
値を代入したいpropertyがある場合、そのproperty名と同一名のclosureにアクセスできるので、closureの引数として代入したい値を渡します。
一通りの値の設定が終わったら.build()
を呼び出すことで、任意の値が代入された状態のインスタンスが返されます。
メソッドにアクセスする場合
インスタンスのメソッドにアクセスしたい場合は、Builderの.reinforce
を介してインスタンスにアクセス
できるようになるため、渡されたインスタンスからメソッドにアクセスします。
let collectionView = UICollectionView().ductTape
.backgroundColor(.red)
.reinforce { $0.register(UITableViewCell.self, forCellWithReuseIdentifier: "Cell") }
.build()
NSObjectを継承していないオブジェクトでも利用する
以下のように、Builderのinitializerの引数にインスタンスを渡すことで利用が可能になります。
class Dog {
var name: String = ""
}
let dog = Builder(Dog())
.name("Copernicus")
.build()
DuctTapeの仕組み
Swift5.1から利用できるKeyPath dynamicMemberLookupを利用して実装してします。
そのためpropertyごとに代入用の処理を実装する必要はなく、subscript<Value>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Value>) -> (Value) -> Builder<Base>
を実装してしまえば、返り値のclosureの引数で受け取った値をKeyPathを介してインスタンスに代入する処理を実現できます。
また、KeyPath dynamicMemberLookupによる実装のため、該当のproperty名が自動補完されます。
@dynamicMemberLookup
public struct Builder<Base: AnyObject> {
private let _build: () -> Base
public init(_ build: @escaping () -> Base) {
self._build = build
}
public init(_ base: Base) {
self._build = { base }
}
public subscript<Value>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Value>) -> (Value) -> Builder<Base> {
{ [build = _build] value in
Builder {
let object = build()
object[keyPath: keyPath] = value
return object
}
}
}
public func build() -> Base {
_build()
}
}