Add observeDefaults to ObservableObject

This commit is contained in:
ribilynn 2023-03-09 18:21:20 +09:00
parent c7211f227d
commit 66e083fc7d
1 changed files with 69 additions and 35 deletions

View File

@ -1,47 +1,57 @@
import Combine
/**
PublishedDefault will trigger `objectWillChange` in the `ObservableObject` when the value is changed.
- Important: By default, `PublishedDefault` does not observe changes made to the corresponding value outside of the `PublishedDefault`.
Only changes made via this `@PublishedDefault` will trigger `objectWillChange`.
To ensure that changes made to Defaults elsewhere also trigger `objectWillChange`, you need to call `observeDefaults` once in the `ObservableObject`.
```swift
extension Defaults.Keys {
static let opacity = Key<Double>("opacity", default: 1)
}
class ViewModel: ObservableObject {
@PublishedDefault(.opacity) var opacity
init() {
observeDefaults()
}
}
```
*/
@propertyWrapper
public struct PublishedDefault<Value: _DefaultsSerializable> {
public class PublishedDefault<Value: _DefaultsSerializable> {
private let key: Defaults.Key<Value>
private var publisher: AnyPublisher<Value, Never>?
private var defaultPublisher: AnyPublisher<Value, Never>?
private var objectSubscription: AnyCancellable?
private var value: Value {
get { Defaults[key] }
set { Defaults[key] = newValue }
}
/**
Get/set a `Defaults` item and also trigger `objectWillChange` in the `ObservableObject` when the value changes.
- Important: Like `@Published`, `@PublishedDefault` does not observe the change of the corresponding value. Only changes made via this `@PublishedDefault` will trigger `ObservableObject`'s `objectWillChange`.
```swift
extension Defaults.Keys {
static let opacity = Key<Double>("opacity", default: 1)
}
class ViewModel: ObservableObject {
@PublishedDefault(.opacity) var opacity
}
```
*/
public init(_ key: Defaults.Key<Value>) {
self.key = key
}
/**
The getter/setter in a `ObservableObject`.
*/
public static subscript<Object: AnyObject>(
_enclosingInstance instance: Object,
wrapped _: ReferenceWritableKeyPath<Object, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<Object, Self>
storage storageKeyPath: ReferenceWritableKeyPath<Object, PublishedDefault>
) -> Value {
get {
instance[keyPath: storageKeyPath].value
}
set {
if let observable = instance as? any ObservableObject,
// Skip if subscrition is already acting
if instance[keyPath: storageKeyPath].objectSubscription == nil,
let observable = instance as? any ObservableObject,
let objectWillChange = observable.objectWillChange as any Publisher as? ObservableObjectPublisher
{
objectWillChange.send()
@ -49,35 +59,33 @@ public struct PublishedDefault<Value: _DefaultsSerializable> {
instance[keyPath: storageKeyPath].value = newValue
}
}
@available(*, unavailable, message: "@Published is only available on properties of AnyObject")
@available(*, unavailable, message: "@PublishedDefault is only available on properties of AnyObject")
public var wrappedValue: Value {
get { value }
set { value = newValue }
}
public var projectedValue: some Publisher<Value, Never> {
mutating get {
if publisher == nil {
publisher = Defaults.publisher(key, options: [.initial])
.map(\.newValue)
.eraseToAnyPublisher()
}
return publisher!
if defaultPublisher == nil {
defaultPublisher = Defaults.publisher(key, options: [.initial])
.map(\.newValue)
.eraseToAnyPublisher()
}
return defaultPublisher!
}
/**
Reset the key back to its default value.
```swift
extension Defaults.Keys {
static let opacity = Key<Double>("opacity", default: 1)
}
class ViewModel: ObservableObject {
@PublishedDefault(.opacity) var opacity
func reset() {
_opacity.reset()
}
@ -88,3 +96,29 @@ public struct PublishedDefault<Value: _DefaultsSerializable> {
key.reset()
}
}
// A type-erase protocol used to subscribe Defaults on ObservableObject.
protocol _PublishedDefaultProtocol {
func subscribe(to publisher: ObservableObjectPublisher)
}
extension PublishedDefault: _PublishedDefaultProtocol {
func subscribe(to publisher: ObservableObjectPublisher) {
objectSubscription = projectedValue
.dropFirst()
.sink { _ in
publisher.send()
}
}
}
public extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {
/**
Begin observing the Default value so that changes made to Defaults outside of the ObservableObject will also trigger objectWillChange.
*/
func observeDefaults() {
for (_, property) in Mirror(reflecting: self).children {
(property as? _PublishedDefaultProtocol)?.subscribe(to: objectWillChange)
}
}
}