Site icon AppTractor

Миграция Ice Cubes на фреймворк SwiftUI Observation

Если вы задавались вопросом, стоит ли вам переводить свое SwiftUI-приложение на новый фреймворк Observation, то вы попали по адресу.

Недавно я перевел Ice Cubes, мой клиент Mastodon с открытым исходным кодом, только на iOS 17+, поскольку полностью перевел его на новые потоки данных фреймворка Observation.

Если вы не знакомы с Ice Cubes, не стесняйтесь прочитать мою предыдущую статью о нем.

Основные выводы

  1. Это не сложное упражнение. Я выполнил его за несколько часов.
  2. Это позволило исправить ошибки, а не создать новые.
  3. Улучшение производительности заметно и стоит того.

В Ice Cubes активно используется ObservableObject, инжектируемый как EnvironmentObject. При прежнем способе работы потока данных любая мутация свойства @Published вызывала обновление представления, независимо от его использования в теле представления. Поэтому нужно было очень внимательно следить за тем, какой EnvironmentObject извлекать и какое property в нем обновлять.

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

Существенное различие между предыдущим потоком данных и новым фреймворком Observation заключается в том, что представления SwiftUI будут себя только тогда, когда в нем используется наблюдаемое свойство. Оно не будет обновляться только потому, что на объект ссылаются в других частях проекта как @State или @Environment.

Вы можете прочитать мой pull request для перехода на Observation в репозитории Ice Cubes здесь.

Производительность SwiftUI

Два момента, касающихся производительности SwiftUI:

  1. При компиляции в Xcode 15/iOS 17 SDK приложение работает гораздо быстрее на устройствах с iOS 16 и 17. Вероятно, это связано с другой версией библиотеки SwiftUI, и Apple, похоже, значительно улучшила производительность. Но это действительно заметно на Ice Cubes. Пользователи хвалят скорость работы приложения после последнего обновления (это было первое обновление, которое я отправил с Xcode 15 RC).
  2. Перенос приложения на фреймворк Observation сделал его еще быстрее. И в этом есть смысл. Временная шкала в Ice Cubes — один из самых сложных видов иерархии приложения. Она отображает статусы с большим количеством данных, которыми можно манипулировать, учитывая различные настройки пользователей. Строка состоит из различных небольших представлений, но имеет массу зависимостей от различных значений окружения.

При профилировании приложения прокрутка таймлайна сейчас вызывает гораздо меньше обновлений свойств и представлений, благодаря тому, что Observable умнее определяет, когда обновлять представление, например, при изменении свойства в модели представления.

И еще одно: не забывайте использовать @ObservationIgnored, если вы не хотите, чтобы представление реагировало на изменение конкретного свойства. Если до этого у вас не было обертки свойства @Published, то, вероятно, это хороший кандидат, чтобы сделать это. Это означает, что оно не будет вызывать обновления представления, даже если вы используете его в теле представления.

@MainActor
@Observable class StatusDetailViewModel {
  enum State {
    case loading, display(statuses: [Status]), error(error: Error)
  }

  var state: State = .loading
  
  @ObservationIgnored
  var isReplyToPreviousCache: [String: Bool] = [:]
}

После преобразования ObservableObject в @Observable представление, отвечающее за хранение объекта, должно использовать @State (вместо @StateObject), но при простой передаче объекта в одно из вложенных представлений никакой специальной аннотации не требуется.

public struct StatusRowView: View {
  @State private var viewModel: StatusRowViewModel

  public init(viewModel: StatusRowViewModel) {
    _viewModel = .init(initialValue: viewModel)
  }
  public var body: some View {
    HStack(spacing: 0) {
      ...
      StatusRowContentView(viewModel: viewModel)
  }
}

struct StatusRowContentView: View {
  var viewModel: StatusRowViewModel

  var body: some View {
    if !viewModel.finalStatus.spoilerText.asRawText.isEmpty {
      @Bindable var viewModel = viewModel
      StatusRowSpoilerView(status: viewModel.finalStatus, displaySpoiler: $viewModel.displaySpoiler)
    }
}

Например, выше показано одно из вложенных представлений моего StatusRowView, на viewModel ссылаются как на простую переменную, и представление все равно будет реагировать на любое изменение свойства, используемого в теле этого представления.

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

Мне пока больше нечего сказать. Фреймворк Observation — это огромное улучшение для SwiftUI. Он прост и избавляет от головной боли по сравнению с предыдущим потоком данных. «Это просто работает», как сказал бы Тодд Говард 🚀.

Источник

Exit mobile version