繁体   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