Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memoization in SwiftUI?

Tags:

swiftui

I come from React where I use useMemo to make sure some computation isn't executed too often. How would I do something like that in SwiftUI?

Consider this example:

struct MyView: View {
    var records: [Record]

    var body: some View {
        Text("expensive summary: \(self.expensiveSummary)")
    }

    var expensiveSummary: String {
        // based on records, return string
        // containing a summary of records

        return ""
    }
}

Is there any way to make sure my expensiveSummary is only called when my array of Records changed?

like image 381
Vojto Avatar asked Sep 07 '25 19:09

Vojto


2 Answers

I think we can create a wrapper view as Equatable. (Perhaps, can we use EquatableView or .equatable() modifier?)

struct UseMemo<KeyType: Equatable, ViewType: View>: View, Equatable {
    let key: KeyType
    let content: (KeyType) -> ViewType

    var body: some View {
        content(key)
    }
    
    static func ==(lhs: UseMemo, rhs: UseMemo) -> Bool {
        return lhs.key == rhs.key
    }
}
struct MyView: View {
    @State var records: [Int] = (Array<Int>)(1...10000)
    @State var other = false

    var body: some View {
        VStack {
            Button("Update other", action: { self.other.toggle() })
            Text("Other: \(String(other))") // This will be called everytime

            Button("Update records", action: { self.records.append(1) })
            
            UseMemo(key: records) { keys in
                // This is not called even if updated "other"
                // This is called when updates "records"
                Text("expensive summary: \(expensiveSummary)")
            }
            
            // This will be called everytime
            Text("expensive summary: \(expensiveSummary)")
        }
    }
    
    var expensiveSummary: String {
        String(records.randomElement()!)
    }
}

Followings are helped me.

like image 80
Kiyoaki Yoshioka Avatar answered Sep 11 '25 02:09

Kiyoaki Yoshioka


This is built-in as EquatableView.

struct MyView: EquatableView { ... }

This will only recompute body if its properties (records in this case) actually change, which will prevent expensiveSummary from being reevaluated.

This won't avoid multiple evaluations of expensiveSummary inside of body. If you need to cache that kind of information, you generally should create a local variable (though sometimes function builder syntax won't allow this without extracting parts of the code into a separate function), or compute a property in init.

As a matter of style, I would recommend against computed properties ever being expensive to evaluate. This is confusing, because property syntax generally suggests the operation is cheap. Instead I would recommend this be a function or computed once in init.

like image 44
Rob Napier Avatar answered Sep 11 '25 02:09

Rob Napier