Implement `DynamicProperty` (#213)

This commit is contained in:
Carson Katri 2020-07-25 17:32:28 -04:00 committed by GitHub
parent a24f49f298
commit c68c70a7d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 62 additions and 15 deletions

View File

@ -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 {
@ -51,7 +51,7 @@ import OpenCombine
}
}
extension AppStorage: DynamicProperty {}
extension AppStorage: ObservedProperty {}
extension AppStorage {
public init(wrappedValue: Value,

View File

@ -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) -> ()
@ -51,6 +51,8 @@ public enum _DefaultSceneStorageProvider {
}
}
extension SceneStorage: ObservedProperty {}
extension SceneStorage {
public init(wrappedValue: Value,
_ key: String) where Value == Bool {

View File

@ -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
}
}

View File

@ -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)
@ -50,3 +50,5 @@ protocol EnvironmentReader {
}
}
}
extension Environment: EnvironmentReader {}

View File

@ -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
@ -63,6 +62,8 @@ import OpenCombine
public init() {}
}
extension EnvironmentObject: ObservedProperty, EnvironmentReader {}
extension ObservableObject {
static var environmentStore: WritableKeyPath<EnvironmentValues, Self?> {
\.[ObjectIdentifier(self)]

View File

@ -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

View File

@ -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) }

View File

@ -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
@ -52,4 +52,4 @@ public struct ObservedObject<ObjectType>: ObservedProperty where ObjectType: Obs
}
}
extension ObservedObject: DynamicProperty {}
extension ObservedObject: ObservedProperty {}

View File

@ -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 }
@ -48,7 +47,6 @@ protocol ValueStorage {
}
extension State: ValueStorage {}
extension State: DynamicProperty {}
extension State where Value: ExpressibleByNilLiteral {
@inlinable public init() { self.init(wrappedValue: nil) }