Skip to content

Instantly share code, notes, and snippets.

@nikitamounier
Last active March 4, 2021 17:19
Show Gist options
  • Save nikitamounier/93a760616de27d57ff237fe3919a3e44 to your computer and use it in GitHub Desktop.
Save nikitamounier/93a760616de27d57ff237fe3919a3e44 to your computer and use it in GitHub Desktop.
Implementation of builder pattern using Swift's powerful keypaths
/// Simple protocol which types who want to implement builder pattern conform to for free – gives conforming types two simple functions for building
protocol Buildable {}
extension Buildable {
/// Returns a new instance whose property defined by the keypath is set to `value`.
/// - Parameters:
/// - keyPath: `WriteableKeyPath` to the property which shall be modified
/// - value: The value which will be set to the property
///
/// This function is used for types with value semantics.
func build<Value>(keyPath: WritableKeyPath<Self, Value>, value: Value) -> Self {
var newSelf = self
newSelf[keyPath: keyPath] = value
return newSelf
}
/// Returns a new instance whose property defined by the keypath is set to `value`.
/// - Parameters:
/// - keyPath: `ReferenceWriteableKeyPath` to the property which shall be modified
/// - value: The value which will be set to the property
///
/// This function is used for types with reference semantics.
func build<Value>(keyPath: ReferenceWritableKeyPath<Self, Value>, value: Value) -> Self {
let newSelf = self
newSelf[keyPath: keyPath] = value
return newSelf
}
}
struct Person {
var name: String?
var age: Int?
var city: String?
var country: String?
var planet: String?
}
// Where the builder functions are defined
extension Person: Buildable {
func called(_ name: String) -> Self {
build(keyPath: \.name, value: name)
}
func aged(_ age: Int) -> Self {
build(keyPath: \.age, value: age)
}
func living(in city: String, country: String, planet: String) -> Self {
build(keyPath: \.city, value: city)
.build(keyPath: \.country, value: country)
.build(keyPath: \.planet, value: planet) // highly composable
}
}
let person = Person()
.called("Chris")
.aged(25)
.living(in: "San Francisco", country: "United States", planet: "Earth")
import UIKit
extension UIView: Buildable {
func backgroundColor(_ color: UIColor) -> Self {
build(keyPath: \.backgroundColor, value: color)
}
func cornerRadius(_ radius: CGFloat) -> Self {
build(keyPath: \.layer.cornerRadius, value: radius)
}
}
// Works great with inheritance
extension UILabel {
func textColor(_ color: UIColor) -> Self {
build(keyPath: \.textColor, value: color)
}
}
let label = UILabel()
.backgroundColor(.blue)
.cornerRadius(25)
.textColor(.white)
// Thanks to fermoya for the original implementation: https://github.com/fermoya/SwiftUIPager/blob/develop/Sources/SwiftUIPager/Helpers/Buildable.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment