在 SwiftUI 中实现昂贵的派生属性的最佳方法是啥?

Posted

技术标签:

【中文标题】在 SwiftUI 中实现昂贵的派生属性的最佳方法是啥?【英文标题】:Best way to implement expensive derived properties in SwiftUI?在 SwiftUI 中实现昂贵的派生属性的最佳方法是什么? 【发布时间】:2021-05-15 08:12:51 【问题描述】:

我的情况有点复杂。我正在 SwiftUI 中实现一个简单的滚动聊天视图。有一个ChatViewChatMessageCells 的列表。每个Message 都有一个User,并且有一个ProfileImage,它是一个由几个字段组成的结构(bucketkey,用于构造服务器上图像的URL)。这可以在User类上定期更新,也可以是nil

更新:这不是一个特别昂贵的操作,但是为了论证的缘故,我们说它很昂贵,我只想重新计算一次。基本上有两种方法:在(其中一个)源属性更改时重新计算,或者在需要时重新计算并存储结果。我想知道两者的最佳方式。

视图需要构造 URL,因为它需要为图像指定所需的图像大小。这样,URL 就是message.user.profileImage 属性的派生属性。

我尝试在我的 ChatMessageCell 视图层次结构中使用 .onChange(of: self.message.user.profileImage) 来计算 URL 并设置 self.profileImageURL,但您不能设置简单的属性。所以我用@State 修饰了self.profileImageURL,它允许代码编译,我将它分配给init()。但是如果是@State,那么这个赋值似乎没有任何作用。

所以,我很不确定该怎么做。

ChatViewChatMessageCell 看起来像这样:

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 的计算属性。您还没有展示MessageUser 的实现,因此很难具体说明。如果Message 是一个结构,那么您可以将用户的引用作为一个可观察对象。

【讨论】:

我编辑了问题以添加其他类。 Welp,我觉得自己像个白痴。计算属性似乎确实有效。但是,有没有办法计算一次(当profileImage 更改时),而不是每次创建新单元格时? User 可以有一个缓存 URL 的字典,以大小为键,当 profileImage 更改时它会清除它。然后它可以根据需要延迟提供特定大小的 URL,但只需计算一次。

以上是关于在 SwiftUI 中实现昂贵的派生属性的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在某些派生类中实现虚函数的正确方法是啥?

在swift中实现swiftUI中的链式语法

有没有更好的方法在 swiftui 中实现抖动动画?

通过迭代基类向量在派生类中实现的调用方法

SwiftUI:如何在 macOS 应用程序中实现编辑菜单

在 java 中实现 PriorityQueue 的最佳方法是啥?