Skip to content

Instantly share code, notes, and snippets.

@sayler8182
Last active May 10, 2023 19:52
Show Gist options
  • Save sayler8182/557c69c0544bcddc7b530631b8921bbe to your computer and use it in GitHub Desktop.
Save sayler8182/557c69c0544bcddc7b530631b8921bbe to your computer and use it in GitHub Desktop.
import UIKit
/* KeyPath extension for listable paths */
protocol KeyPathListable { }
extension KeyPathListable {
var allChildrenCount: Int {
let mirror = Mirror(reflecting: self)
return mirror.children.count
}
var allChildren: [String: Any] {
let mirror = Mirror(reflecting: self)
var description: [String: Any] = [:]
for case let (label?, value) in mirror.children {
description[label] = value
}
return description
}
var allKeyPaths: [PartialKeyPath<Self>: Any] {
var keyPaths: [PartialKeyPath<Self>: Any] = [:]
for (key, value) in allChildren {
let keyPath = \Self.allChildren[key]
keyPaths[keyPath] = value
}
return keyPaths
}
}
/* Builders definition in Buildable.swift */
typealias BuildableStorage<T> = [PartialKeyPath<T>: Any]
protocol Buildable: KeyPathListable {
static func builder() -> Builder<Self>
static func builder(context: Self) -> Builder<Self>
var buildableStorage: BuildableStorage<Self> { get }
var validateBuildableStorage: Bool { get }
init(storage: BuildableStorage<Self>)
}
extension Buildable {
static func builder(context: Self) -> Builder<Self> {
Builder<Self>(context: context)
}
static func builder() -> Builder<Self> {
Builder<Self>()
}
var validateBuildableStorage: Bool { true }
/* Should have default implementation here but probably reflection is required */
/* I didn't find solution how to reflect the object correctly */
// var buildableStorage: BuildableStorage<Self> {
// allKeyPaths
// }
init(context: Self) {
self.init(storage: context.buildableStorage)
}
}
class Builder<T: Buildable> {
var storage: BuildableStorage<T> = [:]
init() { }
init(context: T) {
storage = context.buildableStorage
guard context.validateBuildableStorage else { return }
assert(storage.count == context.allChildrenCount, "All properties should be registered in buildableStorage: \(type(of: context))")
}
func settings<V>(keyPath: KeyPath<T, V>, value: V) -> Self {
storage[keyPath] = value
return self
}
func build() -> T {
T(storage: storage)
}
}
extension Dictionary where Value == Any {
subscript<T, V>(settings keyPath: KeyPath<T, V>) -> V? where Key == PartialKeyPath<T> {
self[keyPath] as? V
}
subscript<T, V>(settings keyPath: KeyPath<T, V?>) -> V? where Key == PartialKeyPath<T> {
self[keyPath] as? V
}
subscript<T, V>(settings keyPath: KeyPath<T, V>,
or defaultValue: V) -> V where Key == PartialKeyPath<T> {
self[settings: keyPath] ?? defaultValue
}
}
/* Models definition in Model.swift */
struct Model {
let name: String
let value: Int?
let child: SubModel?
let constant: Int = 2
var computedProperty: Int {
12 + 13 + 2
}
}
struct SubModel {
let value: Float
var computedProperty: Int {
2 + 13 + 12
}
}
/* Models extension in Model+.swift */
extension Model: Buildable {
var validateBuildableStorage: Bool { false }
var buildableStorage: BuildableStorage<Self> {
var dictionary: BuildableStorage<Self> = [:]
dictionary[\.name] = name
dictionary[\.value] = value
dictionary[\.child] = child
return dictionary
}
init(storage: BuildableStorage<Self>) {
self.init(name: storage[settings: \.name, or: ""],
value: storage[settings: \.value],
child: storage[settings: \.child])
}
}
extension SubModel: Buildable {
var buildableStorage: BuildableStorage<Self> {
var dictionary: BuildableStorage<Self> = [:]
dictionary[\.value] = value
return dictionary
}
init(storage: BuildableStorage<Self>) {
self.init(value: storage[settings: \.value, or: 0.0])
}
}
/* Usage */
let item1 = Model(name: "Item1", value: nil, child: SubModel(value: 0.0))
let item2 = Model.builder()
.settings(keyPath: \.name, value: "Item2")
.settings(keyPath: \.value, value: 1)
.settings(keyPath: \.child, value: SubModel(value: 2.5))
.build()
let item3 = Model.builder(context: item2)
.settings(keyPath: \.name, value: "Item3")
.build()
print(item1) // Item1, nil, 0.0
print(item2) // Item2, 1, 2.5
print(item3) // Item3, 1, 2.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment