Skip to content

Commit

Permalink
Implement DynamicProperty (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
carson-katri authored Jul 25, 2020
1 parent a24f49f commit c68c70a
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 15 deletions.
4 changes: 2 additions & 2 deletions Sources/TokamakCore/App/AppStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import OpenCombine

@propertyWrapper public struct AppStorage<Value>: ObservedProperty {
@propertyWrapper public struct AppStorage<Value>: DynamicProperty {
let provider: _StorageProvider?
@Environment(\._defaultAppStorage) var defaultProvider: _StorageProvider?
var unwrappedProvider: _StorageProvider {
Expand Down Expand Up @@ -51,7 +51,7 @@ import OpenCombine
}
}

extension AppStorage: DynamicProperty {}
extension AppStorage: ObservedProperty {}

extension AppStorage {
public init(wrappedValue: Value,
Expand Down
4 changes: 3 additions & 1 deletion Sources/TokamakCore/App/Scenes/SceneStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public enum _DefaultSceneStorageProvider {
public static var `default`: _StorageProvider!
}

@propertyWrapper public struct SceneStorage<Value>: ObservedProperty {
@propertyWrapper public struct SceneStorage<Value>: DynamicProperty {
let key: String
let defaultValue: Value
let store: (_StorageProvider, String, Value) -> ()
Expand Down Expand Up @@ -51,6 +51,8 @@ public enum _DefaultSceneStorageProvider {
}
}

extension SceneStorage: ObservedProperty {}

extension SceneStorage {
public init(wrappedValue: Value,
_ key: String) where Value == Bool {
Expand Down
42 changes: 40 additions & 2 deletions Sources/TokamakCore/DynamicProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,43 @@
// Created by Carson Katri on 7/17/20.
//

// FIXME: Match SwiftUI implementation
protocol DynamicProperty {}
import Runtime

public protocol DynamicProperty {
mutating func update()
}

extension DynamicProperty {
public mutating func update() {}
}

extension TypeInfo {
/// Extract all `DynamicProperty` from a type, recursively.
/// This is necessary as a `DynamicProperty` can be nested.
/// `EnvironmentValues` can also be injected at this point.
func dynamicProperties(_ environment: EnvironmentValues,
source: inout Any,
shouldUpdate: Bool) -> [PropertyInfo] {
var dynamicProps = [PropertyInfo]()
for prop in properties where prop.type is DynamicProperty.Type {
dynamicProps.append(prop)
// swiftlint:disable force_try
let propInfo = try! typeInfo(of: prop.type)
propInfo.injectEnvironment(from: environment, into: &source)
var extracted = try! prop.get(from: source)
dynamicProps.append(
contentsOf: propInfo.dynamicProperties(environment,
source: &extracted,
shouldUpdate: shouldUpdate)
)
// swiftlint:disable:next force_cast
var extractedDynamicProp = extracted as! DynamicProperty
if shouldUpdate {
extractedDynamicProp.update()
}
try! prop.set(value: extractedDynamicProp, on: &source)
// swiftlint:enable force_try
}
return dynamicProps
}
}
4 changes: 3 additions & 1 deletion Sources/TokamakCore/Environment/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protocol EnvironmentReader {
mutating func setContent(from values: EnvironmentValues)
}

@propertyWrapper public struct Environment<Value>: EnvironmentReader {
@propertyWrapper public struct Environment<Value>: DynamicProperty {
enum Content {
case keyPath(KeyPath<EnvironmentValues, Value>)
case value(Value)
Expand All @@ -50,3 +50,5 @@ protocol EnvironmentReader {
}
}
}

extension Environment: EnvironmentReader {}
5 changes: 3 additions & 2 deletions Sources/TokamakCore/Environment/EnvironmentObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@

import OpenCombine

@propertyWrapper public struct EnvironmentObject<ObjectType>: ObservedProperty,
EnvironmentReader
@propertyWrapper public struct EnvironmentObject<ObjectType>: DynamicProperty
where ObjectType: ObservableObject {
@dynamicMemberLookup public struct Wrapper {
internal let root: ObjectType
Expand Down Expand Up @@ -63,6 +62,8 @@ import OpenCombine
public init() {}
}

extension EnvironmentObject: ObservedProperty, EnvironmentReader {}

extension ObservableObject {
static var environmentStore: WritableKeyPath<EnvironmentValues, Self?> {
\.[ObjectIdentifier(self)]
Expand Down
8 changes: 7 additions & 1 deletion Sources/TokamakCore/StackReconciler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,17 @@ public final class StackReconciler<R: Renderer> {
body bodyKeypath: ReferenceWritableKeyPath<MountedCompositeElement<R>, Any>,
result: KeyPath<MountedCompositeElement<R>, (Any) -> T>) -> T {
let info = try! typeInfo(of: compositeElement.elementType)
info.injectEnvironment(from: compositeElement.environmentValues,
into: &compositeElement[keyPath: bodyKeypath])

let needsSubscriptions = compositeElement.subscriptions.isEmpty

var stateIdx = 0
for property in info.properties {
let dynamicProps = info.dynamicProperties(compositeElement.environmentValues,
source: &compositeElement[keyPath: bodyKeypath],
shouldUpdate: true)
for property in dynamicProps {
// Setup state/subscriptions
if property.type is ValueStorage.Type {
setupState(id: stateIdx, for: property, of: compositeElement, body: bodyKeypath)
stateIdx += 1
Expand Down
2 changes: 1 addition & 1 deletion Sources/TokamakCore/State/Binding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ typealias Updater<T> = (inout T) -> ()
view's state in-place synchronously, but only schedule an update with
the renderer at a later time.
*/
@propertyWrapper public struct Binding<Value> {
@propertyWrapper public struct Binding<Value>: DynamicProperty {
public var wrappedValue: Value {
get { get() }
nonmutating set { set(newValue) }
Expand Down
4 changes: 2 additions & 2 deletions Sources/TokamakCore/State/ObservedObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protocol ObservedProperty: DynamicProperty {
}

@propertyWrapper
public struct ObservedObject<ObjectType>: ObservedProperty where ObjectType: ObservableObject {
public struct ObservedObject<ObjectType>: DynamicProperty where ObjectType: ObservableObject {
@dynamicMemberLookup
public struct Wrapper {
let root: ObjectType
Expand Down Expand Up @@ -52,4 +52,4 @@ public struct ObservedObject<ObjectType>: ObservedProperty where ObjectType: Obs
}
}

extension ObservedObject: DynamicProperty {}
extension ObservedObject: ObservedProperty {}
4 changes: 1 addition & 3 deletions Sources/TokamakCore/State/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
//
// Created by Max Desiatov on 08/04/2020.
//

protocol ValueStorage {
var getter: (() -> Any)? { get set }
var setter: ((Any) -> ())? { get set }
var anyInitialValue: Any { get }
}

@propertyWrapper public struct State<Value> {
@propertyWrapper public struct State<Value>: DynamicProperty {
private let initialValue: Value

var anyInitialValue: Any { initialValue }
Expand All @@ -48,7 +47,6 @@ protocol ValueStorage {
}

extension State: ValueStorage {}
extension State: DynamicProperty {}

extension State where Value: ExpressibleByNilLiteral {
@inlinable public init() { self.init(wrappedValue: nil) }
Expand Down

0 comments on commit c68c70a

Please sign in to comment.