Mimic Swift Combine @Published per creare @PublishedAppStorage
Sto cercando di imitare il wrapper della proprietà Combine @Published. Il mio obiettivo finale è creare un nuovo wrapper di proprietà personalizzato (ad esempio @PublishedAppStorage) di @Published con un @AppStorage nidificato. Quindi ho iniziato cercando di imitare il @Published.
Il mio problema è che si arresta in modo anomalo quando si accede al valore originale dall'interno del blocco sink con l'errore: Thread 1: Simultaneous accesses to 0x600000103328, but modification requires exclusive access
Ho passato giorni a cercare un modo.
Ecco il mio @DMPublished personalizzato:
@propertyWrapper
struct DMPublished<Value> {
private let subject:CurrentValueSubject<Value, Never>
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
self.subject = CurrentValueSubject(wrappedValue)
}
var wrappedValue: Value {
willSet {
subject.send(newValue)
}
}
var projectedValue: AnyPublisher<Value, Never> {
subject.eraseToAnyPublisher()
}
}
L'ObservableObject che definisce le mie proprietà:
import Combine
public class DMDefaults: ObservableObject {
static public let shared = DMDefaults()
private init(){}
@Published public var corePublishedString = "dd"
@DMPublished public var customPublishedString = "DD"
}
Ed ecco la mia funzione di test:
public func testSink()
{
let gdmDefaults = DMDefaults.shared
gdmDefaults.corePublishedString = "ee"; gdmDefaults.customPublishedString = "EE"
gdmDefaults.corePublishedString = "ff"; gdmDefaults.customPublishedString = "FF"
let coreSub = gdmDefaults.$corePublishedString.sink { (newVal) in print("coreSub: oldVal=\(gdmDefaults.corePublishedString) ; newVal=\(newVal)") } let custSub = gdmDefaults.$customPublishedString.sink { (newVal) in
print("custSub: oldVal=\(gdmDefaults.customPublishedString) ; newVal=\(newVal)") // **Crashing here**
}
gdmDefaults.corePublishedString = "gg"; gdmDefaults.customPublishedString = "GG"
}
Apprezzerò qualsiasi aiuto qui ... grazie ...
Risposte
Grazie per la direzione di David, sono riuscito a farlo funzionare. Metto qui il mio ultimo involucro di proprietà nel caso qualcuno lo trovi utile.
@propertyWrapper
public struct PublishedAppStorage<Value> {
@AppStorage
private var storedValue: Value
private var publisher: Publisher?
internal var objectWillChange: ObservableObjectPublisher?
public struct Publisher: Combine.Publisher {
public typealias Output = Value
public typealias Failure = Never
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Value, Downstream.Failure == Never
{
subject.subscribe(subscriber)
}
fileprivate let subject: Combine.CurrentValueSubject<Value, Never>
fileprivate init(_ output: Output) {
subject = .init(output)
}
}
public var projectedValue: Publisher {
mutating get {
if let publisher = publisher {
return publisher
}
let publisher = Publisher(storedValue)
self.publisher = publisher
return publisher
}
}
@available(*, unavailable, message: """
@Published is only available on properties of classes
""")
public var wrappedValue: Value {
get { fatalError() }
set { fatalError() }
}
public static subscript<EnclosingSelf: AnyObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, PublishedAppStorage<Value>>
) -> Value {
get {
return object[keyPath: storageKeyPath].storedValue
}
set {
object[keyPath: storageKeyPath].objectWillChange?.send()
object[keyPath: storageKeyPath].publisher?.subject.send(newValue)
object[keyPath: storageKeyPath].storedValue = newValue
}
}
// MARK:- Initializers
// String
init(wrappedValue: String, _ key: String, store: UserDefaults? = DMDefaults.userDefaults) where Value == String {
self._storedValue = AppStorage(wrappedValue: wrappedValue, key, store: store)
}
// Data
init(wrappedValue: Data, _ key: String, store: UserDefaults? = DMDefaults.userDefaults) where Value == Data {
self._storedValue = AppStorage(wrappedValue: wrappedValue, key, store: store)
}
// Int
init(wrappedValue: Int, _ key: String, store: UserDefaults? = DMDefaults.userDefaults) where Value == Int {
self._storedValue = AppStorage(wrappedValue: wrappedValue, key, store: store)
}
// URL
init(wrappedValue: URL, _ key: String, store: UserDefaults? = DMDefaults.userDefaults) where Value == URL {
self._storedValue = AppStorage(wrappedValue: wrappedValue, key, store: store)
}
// Double
init(wrappedValue: Double, _ key: String, store: UserDefaults? = DMDefaults.userDefaults) where Value == Double {
self._storedValue = AppStorage(wrappedValue: wrappedValue, key, store: store)
}
// Bool
init(wrappedValue: Bool, _ key: String, store: UserDefaults? = DMDefaults.userDefaults) where Value == Bool {
self._storedValue = AppStorage(wrappedValue: wrappedValue, key, store: store)
}
}