Mimic Swift Combine @Published para crear @PublishedAppStorage
Estoy tratando de imitar el contenedor de propiedades Combine @Published. Mi objetivo final es crear un nuevo contenedor de propiedad personalizado (por ejemplo, @PublishedAppStorage) de @Published con un @AppStorage anidado. Así que comencé simplemente tratando de imitar el @Published.
Mi problema es que se bloquea al acceder al valor original desde el bloque del sumidero con el error: Thread 1: Simultaneous accesses to 0x600000103328, but modification requires exclusive access
He pasado días intentando encontrar una forma.
Aquí está mi @DMPublished personalizado:
@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()
}
}
El ObservableObject que define mis propiedades:
import Combine
public class DMDefaults: ObservableObject {
static public let shared = DMDefaults()
private init(){}
@Published public var corePublishedString = "dd"
@DMPublished public var customPublishedString = "DD"
}
Y aquí está mi función de prueba:
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"
}
Agradecería cualquier ayuda aquí ... gracias ...
Respuestas
Gracias por la dirección de David, me las arreglé para que funcionara. Estoy publicando aquí mi envoltorio de propiedad final en caso de que alguien lo encuentre útil.
@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)
}
}