swiftuiswift-data

Changing the SwiftData object by copy in func


As far as I know, any object is passed to a function by copying unless inout is specified

This is how it always worked until I started integrating SwiftData into my app

There is the following code:

import SwiftUI
import SwiftData

struct AccountCircleList: View {
        
    @Query var accounts: [Account]
    @Query var currencies: [Currency]
    
    var groupedAccounts: [Account] {
        return groupAccounts(accounts: tmp, currencies: currencies)
    }
    ...
}

// addictional Q: Maybe there is a way not to transmit exchange currencies directly?
func groupAccounts(accounts: [Account], currencies: [Currency]) -> [Account] {

    /////// this is just illustrative example

    // Before it was "Cash"
    print(accounts[0].name)
    accounts[0].name += "Some" // After this I see "CashSomeSomeSomeSome..." in console

    ///////
    
    var ratesMap: [String: Decimal] {
        Dictionary(uniqueKeysWithValues: currencies.map { ($0.isoCode, $0.rate ) })
    }
        
    for (i, account) in accounts.enumerated() {
        if let parentAccountID = account.parentAccountID {
            let parentAccountIndex = accounts.firstIndex { $0.id == parentAccountID }
            let parentAccount = accounts[parentAccountIndex!]
            
            if account.visible {
                // Here we change the value of the array in usual case
                accounts[parentAccountIndex!].childrenAccounts.append(account)
                if account.accounting {
                    let relation = (ratesMap[parentAccount.currency] ?? 1) / (ratesMap[account.currency] ?? 1)
                    accounts[parentAccountIndex!].budget += account.budget * relation
                    accounts[parentAccountIndex!].remainder += account.remainder * relation
                }
            }
            accounts[i].isChild = true // And here
        }
    }
    return accounts
}

This function groups child accounts into parent accounts and calculates the overall statistics

But it turns out that it changes the overall accounts array, which triggers the grouping function again and again, infinitely, until the memory runs out

Would there be any possibility to do var accountsCopy = accounts.copy()

What I tried:

  • I tried making a subView and calling the groupAccounts() function from it, but it resulted in the same result

  • Assign another variable to accounts in a function and change it: var tmp = accounts

  • Also I make another class Account2 with same fields and convert accounts to accounts2 in func, it helped but it looks ugly

What I expect:

Behavior in which the function will not change the original array and the function will be called only once when opening the screen or changing data in the database


Solution

  • The solution of my problem was the @transient attribute, which says to SwiftData to ignore this field, thereby making the childrenAccounts field with this at attribute and adding two new aggregatedBudget and aggregatedRemainder, I can’t change the “sensitive” data that SwiftData reacts

    @Transient var childrenAccounts: [Account] = []
    @Transient var aggregatedBudget: Decimal = 0
    @Transient var aggregatedRemainder: Decimal = 0