Mimic Swift Combine @Published para criar @PublishedAppStorage
Estou tentando imitar o invólucro da propriedade Combine @Published. Meu objetivo final é criar um novo wrapper de propriedade customizada (por exemplo, @PublishedAppStorage) de @Published com um @AppStorage aninhado. Então, comecei apenas tentando imitar o @Published.
Meu problema que ele trava ao acessar o valor original de dentro do bloco coletor com o erro: Thread 1: Simultaneous accesses to 0x600000103328, but modification requires exclusive access
Passei dias tentando encontrar uma maneira.
Aqui está meu @DMP publicado 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()
}
}
O ObservableObject que define minhas propriedades:
import Combine
public class DMDefaults: ObservableObject {
static public let shared = DMDefaults()
private init(){}
@Published public var corePublishedString = "dd"
@DMPublished public var customPublishedString = "DD"
}
E aqui está minha função de teste:
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"
}
Agradeço qualquer ajuda aqui ... obrigado ...
Respostas
Obrigado pela orientação de David, consegui fazer funcionar. Estou postando aqui meu Wrapper de propriedade final, caso alguém o considere ú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)
}
}