Last active
May 10, 2023 19:52
-
-
Save sayler8182/557c69c0544bcddc7b530631b8921bbe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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