Как этот пример SwiftUI binding + state работает без повторного вызова тела?

Aug 19 2020

Сотрудник придумал следующий пример SwiftUI, который выглядит так, как будто он работает так, как ожидалось (вы можете ввести текст, и он будет отражен ниже), но то, как он работает, меня удивляет!

import SwiftUI

struct ContentView: View {
    @State var text = ""
    var body: some View {
        VStack {
            TextField("Change the string", text: $text) WrappedText(text: $text)
        }
    }
}

struct WrappedText: View {
    @Binding var text: String
    var body: some View {
        Text(text)
    }
}

Моя ментальная модель SwiftUI новичка заставила меня подумать, что ввод в TextField изменит $textпривязку, что, в свою очередь, textизменит @State var. Это сделало бы недействительным ContentView, запустив новый вызов body. Но что интересно, этого не происходит! Установка точки останова в bodyContentView bodyвыполняется только один раз, а WrappedText запускается каждый раз при изменении привязки. И все же, насколько я могу судить, textсостояние действительно меняется.

Итак, что здесь происходит? Почему SwiftUI не вызывает повторно тело ContentView при каждом изменении text?

Ответы

1 Asperi Aug 20 2020 at 04:04

При изменении состояния механизм визуализации SwiftUI сначала проверяет равенство представлений внутри тела и, если некоторые из них не равны, вызывает тело для перестроения, но только эти неравные представления. В вашем случае ни одно представление не зависит (как значение) от textзначения (привязка похожа на ссылку - это то же самое), поэтому на этом уровне нечего перестраивать. Но внутри WrappedTextобнаруживается, что Textwith new textне совпадает со старым text, поэтому WrappedTextвызывается тело объекта для повторного рендеринга этой части.

Это объявленная оптимизация рендеринга SwiftUI - путем проверки и подтверждения точного измененного представления по равенству.

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