在 SwiftUI 中实现昂贵的派生属性的最佳方法是啥?
Posted
技术标签:
【中文标题】在 SwiftUI 中实现昂贵的派生属性的最佳方法是啥?【英文标题】:Best way to implement expensive derived properties in SwiftUI?在 SwiftUI 中实现昂贵的派生属性的最佳方法是什么? 【发布时间】:2021-05-15 08:12:51 【问题描述】:我的情况有点复杂。我正在 SwiftUI 中实现一个简单的滚动聊天视图。有一个ChatView
和ChatMessageCell
s 的列表。每个Message
都有一个User
,并且有一个ProfileImage
,它是一个由几个字段组成的结构(bucket
和key
,用于构造服务器上图像的URL)。这可以在User
类上定期更新,也可以是nil
。
更新:这不是一个特别昂贵的操作,但是为了论证的缘故,我们说它很昂贵,我只想重新计算一次。基本上有两种方法:在(其中一个)源属性更改时重新计算,或者在需要时重新计算并存储结果。我想知道两者的最佳方式。
视图需要构造 URL,因为它需要为图像指定所需的图像大小。这样,URL 就是message.user.profileImage
属性的派生属性。
我尝试在我的 ChatMessageCell
视图层次结构中使用 .onChange(of: self.message.user.profileImage)
来计算 URL 并设置 self.profileImageURL
,但您不能设置简单的属性。所以我用@State
修饰了self.profileImageURL
,它允许代码编译,我将它分配给init()
。但是如果是@State
,那么这个赋值似乎没有任何作用。
所以,我很不确定该怎么做。
ChatView
和 ChatMessageCell
看起来像这样:
struct ChatView : View
@ObservedObject public var stream : ChatStream
var body: some View
ScrollViewReader scrollView in
ScrollView
LazyVStack
ForEach(self.stream.messages) inMsg in
ChatMessageCell(message: inMsg)
struct
ChatMessageCell: View
public let message : ChatStream.Message
@State var profileImageURL : URL?
init(message inMessage: ChatStream.Message)
self.message = inMessage
let image = inMessage.user.profileImage
let url = image?.sharpImageURL(forSize: kProfileImageSize)
self.profileImageURL = url // <-- doesn't assign if @State
var body: some View
VStack(alignment: .leading)
Text("Key: \(self.message.user.profileImage?.key ?? "nil")")
Text("URL: \(self.profileImageURL?.absoluteString ?? "nil")")
// This is just for debugging. Really there's a `KFImage` here that’s
// supposed to async load the image.
.onChange(of: self.message.user.profileImage)
inVal in
let url = inVal?.sharpImageURL(forSize: kProfileImageSize)
self.profileImageURL = url // <-- can't modify if not @State
其他类:
class ChatStream
public struct Message
var id : Int
var date : Date
var message : String
var user : User
init(fromIncoming inMsg: IncomingMessage, user inUser: User)
self.id = inMsg.id
self.date = inMsg.date
self.message = inMsg.message
self.user = inUser
public class User
typealias ID = String
let id : ID
var username : String
var onlineAt : Date?
@Published var profileImage : ProfileImage?
init(fromIncoming inUser: IncomingUser)
self.id = inUser.id
self.username = inUser.username
self.onlineAt = inUser.onlineAt
self.profileImage = inUser.profileImage
func update(fromIncoming inUser: IncomingUser)
self.username = inUser.username
self.onlineAt = inUser.onlineAt
self.profileImage = inUser.profileImage
@Published var messages = OrderedSet<Message>()
@Published var users = [User.ID : User]()
extension ChatStream : ObservableObject
extension ChatStream.ProfileImage : Equatable
【问题讨论】:
【参考方案1】:将message
设置为可观察对象会更直接,它会在用户属性更新时触发更改,并将 URL 设置为message
的计算属性。您还没有展示Message
或User
的实现,因此很难具体说明。如果Message
是一个结构,那么您可以将用户的引用作为一个可观察对象。
【讨论】:
我编辑了问题以添加其他类。 Welp,我觉得自己像个白痴。计算属性似乎确实有效。但是,有没有办法计算一次(当profileImage
更改时),而不是每次创建新单元格时?
User
可以有一个缓存 URL 的字典,以大小为键,当 profileImage 更改时它会清除它。然后它可以根据需要延迟提供特定大小的 URL,但只需计算一次。以上是关于在 SwiftUI 中实现昂贵的派生属性的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章