ObservedObject如何订阅其@Published成员

时间:2020-01-10 00:54:00

标签: swiftui observers combine

我正在尝试编写自己的@Published之类的属性包装器-我有数百个成员值,我只希望在它们实际更改相等性时才发出发布值(而不是每次都设置它们时) ,这是默认设置)

属性包装器本身很简单

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct EqPublished<Value: Equatable> {

    public var wrappedValue: Value
    {
        willSet {
            _projectedValue.send(newValue)
        }
    }
    public var projectedValue: AnyPublisher<Value, Never>
    private var _projectedValue: CurrentValueSubject<Value, Never>

    public init(wrappedValue: Value)
    {
        self.wrappedValue = wrappedValue
        self._projectedValue = CurrentValueSubject<Value, Never>(wrappedValue)
        self.projectedValue = _projectedValue.removeDuplicates().eraseToAnyPublisher()
    }
}

可以与

之类的东西一起使用
class Model: ObservableObject {
    @EqPublished var value = 5
}


// In some view later on 
@State var count = 0
...
Text("El Tappo")
        .onTapGesture {
            // Update every 3rd tap
            self.count = (self.count + 1) % 3
            self.model.value = self.model.value + (self.count == 0 ? 1 : 0)
        }
        .onReceive(self.model.$value) { val in
            print("Value is \(val)")
        }

按预期方式工作-onReceive在第3个抽头上用6触发。EqPublished替换为Published,每次都被调用。

但是-如果我将使用视图更改为

Text("Value is \(model.value)")
        .onTapGesture {
            // Update every 3rd tap
            self.count = (self.count + 1) % 3
            self.model.value = self.model.value + (self.count == 0 ? 1 : 0)
        }
        .onReceive(self.model.objectWillChange) { _ in
            print("Model")
        }

objectWillChange发布者永远不会触发,UI也永远不会重新计算-如果我将@EqPublished更改为简单的@Published,则一切正常。

问题-已更新,以加深理解

  • ObservedObject类如何将自身连接到其发布成员?
  • 何时连接? (我认为在objectWillChange的访问器中,通过查看标头即可)
  • 是否可以进行某种形式的检查来检查每个成员并进行连接而无需关心Value的{​​{1}}类型是什么?

2 个答案:

答案 0 :(得分:1)

应该坚持更长的时间-这在某种程度上是可能的(尽管与Published的使用方式不同)

以下代码更新了上面的代码,以包括一个Void类型的valueWillChange发布者,以允许相同的流程正常工作。而且我没有意识到Swift内置了Mirror反射。

我不能说我知道ObservedObject的工作方式 在没有总体协议或对Void值隐瞒某些发行商的情况下–也许怎么将其作为{{ 1}}类型,却不知道Published<Value>是什么(或忽略它)

这也不难适应可能需要的任何其他条件(小于值验证等)

Value

并更新了UI代码以显示其用法

class Model: EqObservableObject {
    @EqPublished var valueEq = 5
    @Published var valueAlways = 5
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public class EqObservableObject : Combine.ObservableObject {

    private var eqValuesChanging: [AnyCancellable] = []

    init()
    {
        let mirror = Mirror(reflecting: self)
        for child in mirror.children
        {
            if let value = child.value as? EqPublishedProtocol
            {
                eqValuesChanging.append(
                    value.valueWillChange.sink(receiveValue: { [weak self] in
                        self?.objectWillChange.send()
                    })
                )
            }
        }
    }
}

public protocol EqPublishedProtocol
{
    var valueWillChange: AnyPublisher<Void, Never> { get }
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct EqPublished<Value: Equatable>: EqPublishedProtocol
{

    public var wrappedValue: Value
    {
        willSet 
        {
            if wrappedValue != newValue 
            {
                _valueWillChange.send()
            }
        }
        didSet
        {
            if oldValue != wrappedValue
            {
                _projectedValue.send(wrappedValue)
            }
        }
    }

    public var projectedValue: AnyPublisher<Value, Never>
    public var valueWillChange: AnyPublisher<Void, Never>
    private var _projectedValue: CurrentValueSubject<Value, Never>
    private var _valueWillChange: CurrentValueSubject<Void, Never>

    /// Initialize the storage of the Published property as well as the corresponding `Publisher`.
    public init(wrappedValue: Value)
    {
        self.wrappedValue = wrappedValue
        self._projectedValue = CurrentValueSubject<Value, Never>(wrappedValue)
        self.projectedValue = _projectedValue.eraseToAnyPublisher()
        self._valueWillChange = CurrentValueSubject<Void, Never>()
        self.valueWillChange = self._valueWillChange.eraseToAnyPublisher()
    }
}

答案 1 :(得分:0)

尽管我没有回答 ObservedObject如何订阅其@Published成员的实际问题?,但我可以向您展示与your answer不同的方法来实现您想要实现的目标。


如果我没记错的话,则您尝试订阅objectWillChange发布者作为您的自定义属性包装器类型。默认情况下,@Published包装器会自动为您执行此操作。要使用您的包装器类型,您只需手动调用send()上的objectWillChange。因此,您需要订阅定义的属性包装的发布者(预测值),然后在可用的send()发布者上调用objectWillChange方法。

class Model: ObservableObject {
    @EqPublished var valueEq = 5
    @Published var valueAlways = 5

    private var storage = Set<AnyCancellable>()

    init() {
        $valueEq
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }
            .store(in: &storage)
    }
}