Как ObservedObject подписывается на своих участников @Published - PullRequest
1 голос
/ 10 января 2020

Я пытаюсь написать свою собственную @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 срабатывает на третьем касании 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 никогда не сработает, и пользовательский интерфейс никогда не пересчитается - если я изменю @EqPublished на обычный @Published все работает.

Вопрос (и) - обновлен для более глубокого понимания

  • Как класс ObservedObject связывает себя со своими членами публикации?
  • Когда происходит соединение? (Я думаю, что в аксессоре objectWillChange, глядя на заголовки)
  • Есть ли какое-то отражение, которое можно сделать, чтобы осмотреть каждого участника и подключиться, не заботясь о том, что тип Value Published<Value> является?

Ответы [ 2 ]

1 голос
/ 10 января 2020

Должно было остаться на некоторое время дольше - это возможно (хотя и не так, как Published)

Следующий код обновляет приведенный выше код, добавляя тип Void valueWillChange Издатель, чтобы тот же поток работал. И я не осознавал, что в Swift встроено отражение с Mirror.

Не могу сказать, что знаю, как ObservedObject работает без всеобъемлющего протокола или несколько скрытого издателя для значения Void - или, возможно, как преобразовать его в Published<Value> введите, не зная, что такое Value (или игнорируя его)

Это также не составит труда адаптировать к любому другому условию, которое может потребоваться (меньше, чем проверка значения и т. д. c)

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()
    }
}

И обновленный код пользовательского интерфейса, чтобы показать его использование

      HStack {
            Text("ValueEq is \(model.valueEq)")
            .onTapGesture {
                self.count = (self.count + 1) % 3
                self.model.valueEq = self.model.valueEq + (self.count == 0 ? 1 : 0)
            }
            .onReceive(self.model.$valueEq) { val in
                print("ValueEq is \(val)")
            }

            Text("ValueAlways is \(model.valueAlways)")
            .onTapGesture {
                self.count = (self.count + 1) % 3
                self.model.valueAlways = self.model.valueAlways + (self.count == 0 ? 1 : 0)
            }
            .onReceive(self.model.$valueAlways) { val in
                print("ValueAlways is \(val)")
            }
        }
        .onReceive(self.model.objectWillChange) { _ in
            print("Object Changed")
        }
0 голосов
/ 10 января 2020

Хотя я не отвечаю на фактический вопрос Как ObservedObject подписывается на своих @Published участников? , я могу показать вам другой подход, чем ваш ответ для того, что вы хотите достигать.


Если я не ошибаюсь, вы пытаетесь подписаться на 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)
    }
}
...