swiftswiftuicomputed-propertiesswiftdata

recompute of the computed property


Good day, please tell me, is it possible to somehow change the behavior of Computed Property?

What is happening here: we show a list of transactions that are dynamically grouped by dates

import SwiftUI
import SwiftData

struct TransactionsList: View {
    
    @Query(sort: [
        SortDescriptor(\Transaction.dateTransaction, order: .reverse)
    ]) var transactions: [Transaction]
    
    var groupedTransactionByDate: [Date : [Transaction]] {
        print("Grouping transaction")
        return Dictionary(grouping: transactions, by: { $0.dateTransaction })
    }
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(groupedTransactionByDate.keys.sorted(by: >), id: \.self) { date in
                    Section(header: Text(date, style: .date).font(.headline)) {
                        ForEach(groupedTransactionByDate[date]!) { transaction in
                            TransactionRow(transaction: transaction)
                        }
                    }
                }
            }
        }
    }
}

Here the log is printed several thousand times

Here in such a code, if we have 5k transactions in the database, when obtaining the MAP value (27 line), we recalculate the value of the groupedTransactionbyDate variable and allot the memory of 2 GB, which is why oom Killer kills the application

I used to think that the calculated properties count and update only when changing variables that are involved in the calculation, and it turns out to be at every mention of this variable

Possible solutions to the problem

As if it begs to return the two values of Key and Value in the 25th line so that Value can no longer recalculate to each mention, but I did not find a way to do it

I can do when initializing the Fetchdescriptor and fill out the groupedTransactionByDate variable in advance, but then it will not be updated when data changes in the database in background

What did I try to do

I tried to make the simplest model with two fields and try to group it also, which excludes the change in this model from the outside, but this has led to the fact that for each receipt of the computed properties we recalculate

What behavior I need

  1. When the screen is opened, once we take data from the database (when the data changes in the database, we will recalculate the entire screen)

  2. Once a variable groupedTransactionByDate will be counted

  3. Once we will draw the screen until it changes/add/delete at least one transaction in the database, after which 1, 2 and 3 points are again counted


Solution

  • One solution is to store the grouped transactions in a local variable in body. This guarantees that the computation is done exactly once each time SwiftUI asks TransactionsList for its body.

    struct TransactionsList: View {
        
        @Query(sort: [
            SortDescriptor(\Transaction.dateTransaction, order: .reverse)
        ]) var transactions: [Transaction]
        
        var body: some View {
            let groupedTransactionByDate = Dictionary(
                grouping: transactions,
                by: { $0.dateTransaction }
            )
        
            NavigationStack {
                List {
                    ForEach(groupedTransactionByDate.keys.sorted(by: >), id: \.self) { date in
                        Section(header: Text(date, style: .date).font(.headline)) {
                            ForEach(groupedTransactionByDate[date]!) { transaction in
                                TransactionRow(transaction: transaction)
                            }
                        }
                    }
                }
            }
        }
    }