簡體   English   中英

SwiftUI-ObservableObject性能問題

[英]SwiftUI - ObservableObject performance issues

當SwiftUI View綁定到ObservableObject ,如果觀察到的對象發生任何更改,則視圖會自動重新加載-不管更改是否直接影響視圖。

對於非平凡的應用程序,這似乎會導致嚴重的性能問題。 看這個簡單的例子:

// Our observed model
class User: ObservableObject {
    @Published var name = "Bob"
    @Published var imageResource = "IMAGE_RESOURCE"
}


// Name view
struct NameView: View {
    @EnvironmentObject var user: User

    var body: some View {
        print("Redrawing name")
        return TextField("Name", text: $user.name)
    }
}

// Image view - elsewhere in the app
struct ImageView: View {
    @EnvironmentObject var user: User

    var body: some View {
        print("Redrawing image")
        return Image(user.imageResource)
    }
}

在這里,我們有兩個不相關的視圖,分別位於應用程序的不同部分。 他們都觀察到環境提供的共享User更改。 NameView允許您通過TextField編輯User名。 ImageView顯示用戶的個人資料圖像。

屏幕截圖

問題:NameView每次擊鍵NameView所有觀察該User視圖都必須重新加載其整個正文內容。 這包括ImageView ,它可能涉及一些昂貴的操作-例如下載/調整大圖像的大小。

在上面的示例中可以很容易地證明這一點,因為每次在TextField中輸入新字符時都會記錄"Redrawing name""Redrawing image"

問題:我們如何改善對Observable / Environment對象的使用,以避免不必要的重繪視圖? 有沒有更好的方法來構建我們的數據模型?

編輯:

為了更好地說明這可能是個問題的原因,假設ImageView所做的不僅僅是顯示靜態圖像。 例如,它可能:

  • 異步加載圖片,由子視圖的initonAppear方法觸發
  • 包含正在運行的動畫
  • 支持拖放界面,需要本地狀態管理

還有更多示例,但這是我在當前項目中遇到的示例。 在每種情況下,重新計算視圖的body都將導致廢棄狀態,並且某些昂貴的操作將被取消/重新啟動。

並不是說這是SwiftUI中的“錯誤”-但是,如果有更好的方法來構建我們的應用程序,我還沒有看到Apple或任何教程提到過它。 大多數示例似乎都贊成在不解決副作用的情況下自由使用EnvironmentObject。

為什么ImageView需要整個User對象?

答:不是。

對其進行更改以僅使用所需的內容:

struct ImageView: View {
    var imageName: String

    var body: some View {
        print("Redrawing image")
        return Image(imageName)
    }
}

struct ContentView: View {
    @EnvironmentObject var user: User

    var body: some View {
        VStack {
            NameView()
            ImageView(imageName: user.imageResource)
        }
    }
}

當我點擊鍵盤按鍵時輸出:

Redrawing name
Redrawing image
Redrawing name
Redrawing name
Redrawing name
Redrawing name

一個快速的解決方案是使用debounce(for:scheduler:options :)

當您要等待上游發布者傳遞事件的暫停時,請使用此運算符。 例如,在發布者上從文本字段調用反跳操作以僅在用戶暫停或停止鍵入時才接收元素。 當他們再次開始鍵入時,防跳動將保持事件傳遞直到下一個暫停。

我已經快速完成了這個小例子,展示了使用它的方法。

// UserViewModel
import Foundation
import Combine

class UserViewModel: ObservableObject {
  // input
  @Published var temporaryUsername = ""

  // output
  @Published var username = ""

  private var temporaryUsernamePublisher: AnyPublisher<Bool, Never> {
    $temporaryUsername
      .debounce(for: 0.5, scheduler: RunLoop.main)
      .removeDuplicates()
      .eraseToAnyPublisher()
  }


  init() {
    temporaryUsernamePublisher
      .receive(on: RunLoop.main)
      .assign(to: \.username, on: self)    
  }
}

// View
import SwiftUI

struct ContentView: View {

  @ObservedObject private var userViewModel = UserViewModel()

  var body: some View {
    TextField("Username", text: $userViewModel.temporaryUsername)
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

希望對您有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM