[英]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
所做的不僅僅是顯示靜態圖像。 例如,它可能:
init
或onAppear
方法觸發 還有更多示例,但這是我在當前項目中遇到的示例。 在每種情況下,重新計算視圖的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.