SwiftUI постепенно набирает силу и становится предпочтительным выбором для создания приложений. В этом посте мы рассмотрим важные моменты, которые помогут вам повысить производительность и эффективность разработки приложений.
Redux или MVVM?
Redux обеспечивает централизованный подход к управлению состоянием. В качестве наблюдаемого объекта на корневом уровне создается одна переменная Store, которая хранит состояние всего приложения. Мы передаем эту переменную Store каждому представлению как Environment объект. Это означает, что представление SwiftUI будет обновляться при изменении переменной Store.
В результате одно изменение переменной может обновить все представления приложения, что, в свою очередь, снижает производительность приложения, когда модулей становится больше.
MVVM, напротив, позволяет создавать @ObservableObject для каждого представления отдельно, упрощая проекты SwiftUI. Такая настройка гарантирует, что изменения в observableObject будут обновлять только конкретное связанное представление, что делает обновление более эффективным и точным.
Используйте @StateObject и @ObservableObject
@StateObject
особенно полезен для представлений верхнего уровня, которые существуют недолго. С другой стороны, @ObservedObject
обычно используется, когда вы хотите совместно использовать один и тот же экземпляр объекта в нескольких представлениях.
В приведенном ниже примере модель TapViewModel инициализируется каждый раз при обновлении RandomNumberView. Преобразование @ObservableObject
в @StateObject
просто решает эту проблему.
class TapViewModel: ObservableObject { @Published private(set) var taps = 0 func incrementTaps() { taps += 1 } } struct TapView: View { @ObservedObject var viewModel = TapViewModel() var body: some View { VStack { Text("Total Taps: \(viewModel.taps)") Button("Increment Taps") { viewModel.incrementTaps() } } } } struct RandomNumberView: View { @State var randomNumber = 0 var body: some View { VStack { Text("Random number is: \(randomNumber)") Button("Randomize number") { randomNumber = (0..<1000).randomElement()! } } .padding(.bottom) TapView() } }
Создавайте @Published переменные с осторожностью
Чрезмерное использование свойства @Published
может привести к ненужным обновлениям представлений, что может повлиять на производительность приложения. Чрезмерное использование этого свойства может привести к тому, что представления будут обновляться чаще, чем нужно, ищбыточно потребляя ресурсы.
Альтернативы свойству @Published
:
- Создайте переменную без использования свойства
@Published
в классеObservableObject
и используйтеobjectWillChange.send()
каждый раз, когда вам нужно обновить пользовательский интерфейс. - Создайте переменную
@State
в представлении, которая будет отвечать за обновление представления только при необходимости, как показано ниже:
class ViewModel: ObservableObject { var totalTaps = 0 } struct ContentView: View { @StateObject private var viewModel = ViewModel() @State private var refreshUI = false var body: some View { VStack { Text("Total Taps: \(viewModel.totalTaps)") Button { viewModel.totalTaps += 1 // add other operation which can change the @Published variable. // After all, just change the refreshUI variable to refresh the view only once. refreshUI.toggle() } label: { Text("Tap Here!!") } } .overlay(refreshUI ? EmptyView() : EmptyView()) } }
Разбивайте содержимое body на более мелкие части для минимизации времени компиляции
В SwiftUI есть ограничение на добавление только 10 представлений в каждом стеке. На это есть своя причина. Прямое добавление нескольких представлений в стек может увеличить время компиляции. Лучше создать отдельную переменную/функцию для вложенных представлений и вызывать их в body
.
Предпочтите @State для анимации вместо @Published
@State
управляет локальным состоянием и обновляет его внутри представления. SwiftUI оптимизирован для эффективной работы с переменными @State
. Когда переменная изменяется, SwiftUI автоматически пересчитывает затронутые части иерархии представлений. Это приводит к лучшей производительности и более плавной анимации.
С другой стороны, @Published
обычно используется в контексте паттерна Observable Object, который больше подходит для обмена данными между различными представлениями или управления данными в структуре, подобной модели. Хотя вы можете использовать @Published
с анимацией, он может не обеспечить такой же уровень производительности и предсказуемости анимации, как @State
.
Объявляйте объекты Observable в определенном месте, чтобы избежать ненужного обновления UI
Обновление пользовательского интерфейса чаще, чем это необходимо, может привести к проблемам с производительностью. Объявляя наблюдаемые объекты в стратегически важных местах, вы можете минимизировать количество обновлений и гарантировать, что только релевантные изменения вызывают обновление пользовательского интерфейса.
В приведенном ниже примере для получения количества тиков используется объект observable. Вместо того чтобы объявить его в ContentView, мы объявили его в TickView. Таким образом, ответственность за ежесекундное обновление лежит только на TickView, избавляя другие представления от обновления пользовательского интерфейса. Эта стратегическая корректировка способствует повышению производительности приложения и общей плавности.
import SwiftUI import Combine class ViewModel: ObservableObject { static let shared = ViewModel() private var cancellable: [AnyCancellable] = [] @Published var ticks = 0 private init() { Timer.publish(every: 1, on: .main, in: .common) .autoconnect() .sink(receiveValue: { [weak self] _ in self?.ticks += 1 }) .store(in: &cancellable) } } struct ContentView: View { var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) TickView() } .padding() } } struct TickView: View { @StateObject private var viewModel = ViewModel.shared var body: some View { Text("Total Ticks \(viewModel.ticks)") } }
Найдите, какое изменение данных вызывает обновление представления SwiftUI
Если вы добавляете .onChange
и .onReceive
, чтобы проверить, какая переменная отвечает за обновление пользовательского интерфейса, не делайте этого! SwiftUI предоставляет специальный метод, предназначенный только для отладки, чтобы определить, какое изменение вызвало перезагрузку представления.
Self._printChanges()
следует вызвать внутри свойства body
, и вы сможете увидеть логи изменений в консоли.
Как и в примере выше, мы можем написать операторы печати внутри body
, чтобы проверить текущее значение переменных.
struct TickView: View { @StateObject private var viewModel = ViewModel.shared var body: some View { VStack { let _ = print("Tick is:", viewModel.ticks) let _ = Self._printChanges() Text("Total Ticks \(viewModel.ticks)") } } } /* Console logs: Tick is: 0 TickView: @self, @identity, _viewModel changed. Tick is: 1 TickView: _viewModel changed. Tick is: 2 TickView: _viewModel changed. */
Используйте LazyHStack и LazyVStack, если у вас длинный список
Представления LazyHStack и LazyVStack предлагают оптимизированное управление памятью и рендеринг, что приводит к улучшению производительности и отзывчивости вашего приложения.
Есть ли что-нибудь похожее на viewDidLoad()?
Вы можете заменить модификатор .onAppear
на .onLoad
, если ваша логика должна вызываться только один раз.
import SwiftUI struct ViewDidLoadModifier: ViewModifier { @State private var didLoad = false private let action: (() -> Void)? init(perform action: (() -> Void)? = nil) { self.action = action } func body(content: Content) -> some View { content.onAppear { if didLoad == false { didLoad = true action?() } } } } extension View { func onLoad(perform action: (() -> Void)? = nil) -> some View { modifier(ViewDidLoadModifier(perform: action)) } }
Используйте UIKit компоненты непосредственно в SwiftUI
Если у вас нет большого потока данных между представлением SwiftUI и компонентом UIKit, этот модификатор поможет разместить компонент UIKit непосредственно в представлении SwiftUI.
extension UIView { var swiftUI: some View { ViewWrapper(view: self) } } private struct ViewWrapper: UIViewRepresentable { let view: UIView func makeUIView(context: Context) -> UIView { view } func updateUIView(_ uiView: UIView, context: Context) { } } // usage class UIKItLabel: UILabel { required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! self.text = "Hey There" } override init(frame: CGRect) { super.init(frame: frame) self.text = "Hey There" } } struct ContentView: View { var body: some View { VStack { UIKItLabel() .swiftUI } } }
Модификатор с условиями
Добавлять условия в модификатор не так просто. Вы можете использовать тернарный оператор, но вам придется подтвердить оба утверждения if/else. А что, если вам нужно только утверждение if? Приведенный ниже модификатор поможет вам в этом:
extension View { @ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View { if condition { transform(self) } else { self } } } // usage struct TickView: View { @State private var ticks = 0 var body: some View { VStack { Text("Total Ticks \(ticks)") .if(ticks == 0) { $0.layoutPriority(1) } } } }
Избегайте совместного использования .padding и .frame
Сочетание модификаторов .padding
и .frame
в одном представлении может привести к избыточным вычислениям в макете, что вызовет снижение производительности.
Используйте GeometryReader с умом
Представление GeometryReader предоставляет доступ к размерам и расположению родительского представления, однако его использование может привести к снижению производительности. Это связано с необходимостью пересчитывать компоновку для всех дочерних представлений при каждом изменении в родительском представлении. Поэтому используйте его с осторожностью и в ситуациях, когда он действительно необходим.
Эпизодическое использование AnyView
В SwiftUI вы можете использовать тип AnyView для отображения различных типов представлений по мере необходимости. Но если вы будете использовать AnyView слишком часто, это может сделать ваш код менее безопасным и замедлить дальнейшее повышение производительности.
Обязательное используйте ID в Foreach и List
Когда вы используете ForEach
для создания списка представлений, обязательно включайте параметр «id». Это поможет SwiftUI узнать, какой элемент к какому относится, чтобы понять, когда добавлять, обновлять или удалять представления при изменении списка. Благодаря этому ваше приложение будет работать быстрее и плавнее.
Предпочитайте использовать текст, а не метку
Text
— это легкое представление, оптимизированное для отображения небольших объемов текста, в то время как Label
— более тяжелое представление, подходящее для отображения больших объемов текста или смешанного контента. Это может помочь повысить производительность вашего приложения, особенно при отображении большого количества текста.
Используйте Group для возврата нескольких экземпляров представления
Если одно и то же свойство будет применяться к нескольким представлениям, лучше всего добавить их в группу и применить общее свойство только один раз.
Используйте .fixedSize(), чтобы указать, что фрейм не изменяется
Если у вас есть представления с одинаковыми размерами, вы можете использовать модификатор .fixedSize()
, чтобы задать их размеры. Это поможет SwiftUI пропустить ненужные вычисления макета, что приведет к повышению производительности.
Напутственные слова
Эти рекомендации основаны исключительно на нашем опыте. Хотя этот список не является исчерпывающим для SwiftUI, он поможет вам начать свой путь к созданию отличных и плавных приложений.