Mimic Swift 结合@Published 创建@PublishedAppStorage
Mimic Swift Combine @Published to create @PublishedAppStorage
我正在尝试模仿 Combine @Published 属性 包装器。我的最终目标是使用嵌套的 @AppStorage 创建一个新的自定义 属性 包装器(例如 @PublishedAppStorage)@Published。
所以我只是开始尝试模仿@Published。
我的问题是在从接收器块中访问原始值时崩溃并出现错误:
Thread 1: Simultaneous accesses to 0x600000103328, but modification requires exclusive access
我花了几天时间寻找方法。
这是我的自定义@DMPublished:
@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()
}
}
定义我的属性的 ObservableObject:
import Combine
public class DMDefaults: ObservableObject {
static public let shared = DMDefaults()
private init(){}
@Published public var corePublishedString = "dd"
@DMPublished public var customPublishedString = "DD"
}
这是我的测试函数:
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"
}
在此感谢任何帮助...谢谢...
感谢 David 的指导,我已经成功完成了。我在这里发布我的最终 属性 包装,以防有人觉得它有帮助。
@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)
}
}
我正在尝试模仿 Combine @Published 属性 包装器。我的最终目标是使用嵌套的 @AppStorage 创建一个新的自定义 属性 包装器(例如 @PublishedAppStorage)@Published。 所以我只是开始尝试模仿@Published。
我的问题是在从接收器块中访问原始值时崩溃并出现错误:
Thread 1: Simultaneous accesses to 0x600000103328, but modification requires exclusive access
我花了几天时间寻找方法。
这是我的自定义@DMPublished:
@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()
}
}
定义我的属性的 ObservableObject:
import Combine
public class DMDefaults: ObservableObject {
static public let shared = DMDefaults()
private init(){}
@Published public var corePublishedString = "dd"
@DMPublished public var customPublishedString = "DD"
}
这是我的测试函数:
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"
}
在此感谢任何帮助...谢谢...
感谢 David 的指导,我已经成功完成了。我在这里发布我的最终 属性 包装,以防有人觉得它有帮助。
@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)
}
}