将自定义属性包装器与@Published

问题描述 投票:0回答:3

我希望将自定义属性包装器应用到已包装在

@Published
中的变量,将它们嵌套起来就像
(A)
@Custom @Published var myVar

(B)
@Published @Custom var myVar

(注意包装纸的应用顺序)。

(A) 的情况下,我收到错误

'wrappedValue' is unavailable: @Published is only available on properties of classes

(B)

error: key path value type 'Int' cannot be converted to contextual type 'Updating<Int>'

两者都不是特别有帮助。有什么想法如何让它发挥作用吗?

最小代码示例

import Combine

class A {
    @Updating @Published var b: Int
    
    init(b: Int) {
        self.b = b
    }
}

@propertyWrapper struct Updating<T> {
    var wrappedValue: T {
        didSet {
            print("Update: \(wrappedValue)")
        }
    }
}

let a = A(b: 1)
let cancellable = a.$b.sink {
    print("Published: \($0)")
}
a.b = 2
// Expected output:
// ==> Published: 1
// ==> Published: 2
// ==> Update: 2
swift combine property-wrapper
3个回答
2
投票

您提供的任何选项都不能应用于使自定义属性包装器的行为像用@Published(A和B)标记的那样

真正的问题是,我如何观察属性/状态更新变化?

  1. 使用@Published包装器,自动处理状态更新

  2. 通过实施

    手动跟踪状态更新

    willSet {objectWillChange.send()}

考虑到选项 1 无法工作,因为您无法应用 2 个属性包装器, 您可以进行手动状态更新跟踪。 为了实现这一点,您需要使您的类符合 ObservableObject 协议。

class A: ObservableObject {
    @Updating var b: Int{
       willSet {objectWillChange.send()}
    }
    
    init(b: Int) {
        self.b = b
    }
}

现在,只要 var b 发生更改,您的视图就可以刷新,同时您的 @Updating 包装器完全正常工作。


2
投票

我找到的唯一解决方案是一种解决方法:制作一个自定义

@propertyWrapper
,其中包含
@Published
属性。

示例:

/// Workaround @Published not playing nice with other property wrappers. 
/// Use this to replace @Published to temporarily help debug a property being accessed off the main thread.

@propertyWrapper
public class MainThreadPublished<Value> {
    @Published
    private var value: Value
    
    
    public var projectedValue: Published<Value>.Publisher {
        get {
            assert(Thread.isMainThread, "Accessing @MainThread property on wrong thread: \(Thread.current)")
            return $value
        }
        @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
        set {
            assert(Thread.isMainThread, "Accessing @MainThread property on wrong thread: \(Thread.current)")
            $value = newValue
        }
    }
    
    public var wrappedValue: Value {
        get {
            assert(Thread.isMainThread, "Accessing @MainThread property on wrong thread: \(Thread.current)")
            return value
        }
        set {
            assert(Thread.isMainThread, "Accessing @MainThread property on wrong thread: \(Thread.current)")
            value = newValue
        }
    }
    
    public init(wrappedValue value: Value) {
        self.value = value
    }

    public init(initialValue value: Value) {
        self.value = value
    }
}

进一步阅读:


编辑:

我也刚刚发现这篇文章可能提供替代方法,但我没有时间调查:


0
投票

我尝试了 MechEthan 的解决方案,但它对我不起作用......并且我通过 Simone 的回答发现我仍然需要发送

objectWillChange
信号。所以我最终得到了这个:

@propertyWrapper
class _CustomPublished<Instance: ObservableObject, Value> where Instance.ObjectWillChangePublisher == ObservableObjectPublisher {
    @available(*, unavailable)
    var wrappedValue: Value {
        get { fatalError() }
        set { fatalError() }
    }

    var projectedValue: Published<Value>.Publisher {
        get { $value }
        set { $value = newValue }
    }

    static subscript(
        _enclosingInstance instance: Instance,
        wrapped _: KeyPath<Instance, Value>,
        storage storageKeyPath: KeyPath<Instance, _CustomPublished>
    ) -> Value {
        get { instance[keyPath: storageKeyPath].value }
        set {
            instance.objectWillChange.send()
            instance[keyPath: storageKeyPath].value = newValue
        }
    }

    init(wrappedValue: Value) {
        value = wrappedValue
    }

    @Published private var value: Value
}

extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher {
    typealias CustomPublished<T> = _CustomPublished<Self, T>
}

我在我的项目中替换了一些

@Published
,它似乎有效,但不确定是否还有其他隐藏的差异

确实,Swift 在编写属性包装器方面应该做得更好

© www.soinside.com 2019 - 2024. All rights reserved.