Изменение свойства «Published» ObservableObject на вычисляемую переменную.

У меня есть свойства:

      class SettingsViewState: ObservableObject {
    @Published var viewData: SettingsViewData = .init()
    …

Я хотел бы перейти на вычисляемую переменную, основанную на других источниках истины, вместо того, чтобы позволять ее напрямую изменять. Тем не менее, я все еще хочу, чтобы просмотренные представления автоматически обновлялись по мере их изменения. Они должны обновляться, когда свойства вычисляются из изменения.

Хотя я действительно не очень уверен в том, как на самом деле это работает. Я думаю, что это имеет своеwillSetвыполнить вложение до того, как произойдет изменение, но я не уверен!

Если мои подозрения верны, похоже, я мог бы вручную вызвать включающий объект, если что-то от него изменится.

Альтернативно, если свойстваviewDataвычисляется из самих себя, когда я меняю один, предположительно эквивалентobjectWillChange.send()произойдет автоматически, и мне не нужно будет делать ничего особенного? Это должно работать, даже если эти свойстваprivateи наблюдающее представление не имеет к ним доступа: оно все равно должно видетьobjectWillChangeизлучается?

Однако вполне возможно, что я написал это ужасно искаженно или в основном наоборот! Например, возможно,@Publishedсвойства имеют собственного независимого издателя изменений, а не просто используют прилагаемый файлObservableObjectх? Или они оба публикуются до изменения?

Разъяснения будут приняты с благодарностью. Спасибо!

3 ответа

Я действительно не очень уверен в том, как на самом деле работает @Published. Я думаю, что у него есть метод willSet, выполняющий objectWillChange.send() для окружающего ObservableObject до того, как произойдет изменение, но я не уверен!

Вы правы, именно так и работают вместе, однако это 2 независимые вещи.

— это оболочка свойства, которая добавляетPublisherк обернутому свойству, которое выдает новое значение изwillSetсобственности. ИтакPublished.Publisherвыдает значение, которое должно быть установлено, прежде чем оно будет фактически установлено.

— это протокол, у которого есть издатель, значение которого автоматически синтезируется компилятором. Синтезированная реализация выдает значение, когда выдает его любой из издателей свойства. Таким образом, выдается значение всякий раз, когда свойство соответствующего типа собирается измениться.

Если вы сохраняете соответствующий тип в SwiftUIViewкак@StateObject,@ObservedObjectили@EnvironmentObject, представление обновляется (и, следовательно, пересчитывает своеbody) всякий раз, когдаobjectWillChangeпринадлежащийObservableObjectизлучает.

Если у вас есть свойство, которое необходимо пересчитывать при каждом обновлении других свойств, и вы хотите обновить свое представление с учетом этих изменений, у вас есть несколько вариантов добиться этого.

  1. Объявите все свойства как хранимые свойства и настройте зависимое свойство на обновление при каждом обновлении любого из свойств, от которых оно зависит.

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

Это решение также работает, даже если свойства, от которых зависит ваше «вычисляемое» свойство, объявлены в других типах, поскольку ваше «вычисляемое» свойство таково, когда оно обновляется, оно запускает обновление представления.

Однако вам необходимо настроить наблюдение за зависимыми свойствами, чтобы обновлять свойство, которое от них зависит.

      @Published var height: Int
@Published var width: Int

@Published private(set) var size: Int

init(height: Int, width: Int) {
  self.height = height
  self.width = width
  self.size = height * width
  $height.combineLatest($width).map { height, width in
    height * width
  }.assign(to: &$size)
}
  1. Если все свойства, от которых зависит ваше вычисляемое свойство, объявлены в том же объекте, что и ваше вычисляемое свойство, простое объявление свойства, которое зависит от них, как вычисленное, должно работать, поскольку свойства, от которых вы зависите, сами будут запускать обновление представления всякий раз, когда они обновляются.

Однако это не работает, если ваш@Publishedсвойства объявляются для другого объекта, поскольку этот объект не вызывает обновление представления.

Используйте для просмотра данных (значений или структур с изменяющимися функциями).ObservableObjectизначально был разработан для срока службы данных модели.

В структуре View используйте вычисляемое свойство для преобразования данных при их передаче дочернему элементу.Viewструктуры. Обычно мы преобразуем расширенные типы моделей в простые значения, которые передаются в доступные для предварительного просмотра представления.

Более позднее добавление@StateObjectпредназначен для случаев, когда вам нужен ссылочный тип в@Stateчто необычно и, похоже, здесь не требуется.

Я пытался ответить на этот же вопрос для себя и попал сюда. Я знаю, что проблема уже «решена» уже пару месяцев, но я нашел другой способ, который работает, по крайней мере, для моего сценария.

Вместо того, чтобы вычислять общедоступное свойство, вы можете возложить работу на частные свойства, чтобы обновлять общедоступные свойства всякий раз, когда они обновляются с использованием их файлов .

      import Foundation
    
class ThemeList: ObservableObject {
    // this is the property I wanted to be computed
    @Published public private(set) var themes: [String]
        
    // these are the "sources of truth"
    private let internalThemes: [String]
    private var customThemes: [String] {
        // this is where the magic happens!
        didSet {
            bundleThemes()
        }        
    }
    
    init(themes: [String], customThemes: [String] = []) {
        self.internalThemes = themes
        self.customThemes = customThemes
        self.themes = []
        bundleThemes()
    }
    
    // set the (no-longer computed) @Published property
    // triggering the 
    func bundleThemes() {
        var allThemes: [String]
        allThemes = internalThemes
        allThemes.append(contentsOf: customThemes)
        allThemes.sort()
        themes = allThemes
    }
    
    func addTheme(_ theme: String) {
        customThemes.append(theme)
    }
}
    
let themeList = ThemeList(themes: ["Hi", "Hello"])
let cancellable = themeList.$themes
    .sink() {
        print ($0)
}
    
themeList.addTheme("Howdy!")

Вы можете запустить это как есть на игровой площадке и увидеть, что новое значениепубликуется.

Изначально я пытался сделатьвычисляемое свойство, которое будет объединять и сортировать два частных свойства (внутренние и пользовательские темы). Конечно, вы не можете использоватьпо вычисленному свойству. Поэтому вместо этого вы делаете это общедоступным, получаете частную собственность и используетечтобы обновить его. Это приводит к сохранению обновления свойства, как если бы оно было вычислено, а также к отправке уведомления об изменении.

Другие вопросы по тегам