SwiftData对象传参未用inout却修改原数组的问题求助
问题根源
你遇到的问题核心是SwiftData的@Model类属于引用类型,数组赋值(比如var tmp = accounts)只是复制了数组中对象的引用,而非对象本身的深拷贝。所以你修改数组里的元素时,实际操作的还是原SwiftData存储中的实例,进而触发@Query的更新,导致groupedAccounts反复计算,最终内存耗尽。
解决方案1:实现Model对象的深拷贝
给Account模型添加深拷贝方法,手动复制所有属性(包括嵌套集合),确保操作的是独立副本:
extension Account { func deepCopy() -> Account { let copy = Account() // 复制所有基础属性 copy.id = self.id copy.name = self.name copy.parentAccountID = self.parentAccountID copy.currency = self.currency copy.budget = self.budget copy.remainder = self.remainder copy.visible = self.visible copy.accounting = self.accounting copy.isChild = self.isChild // 深拷贝子账户(根据业务逻辑调整) copy.childrenAccounts = self.childrenAccounts.map { $0.deepCopy() } return copy } }
然后在groupAccounts函数开头,先创建整个数组的深拷贝,后续操作都基于这个副本:
func groupAccounts(accounts: [Account], currencies: [Currency]) -> [Account] { // 生成独立副本数组 var accountsCopy = accounts.map { $0.deepCopy() } // 示例代码修改为操作副本 print(accountsCopy[0].name) accountsCopy[0].name += "Some" // 仅修改副本,不影响原数据 var ratesMap: [String: Decimal] { Dictionary(uniqueKeysWithValues: currencies.map { ($0.isoCode, $0.rate ) }) } for (i, account) in accountsCopy.enumerated() { if let parentAccountID = account.parentAccountID { guard let parentIndex = accountsCopy.firstIndex({ $0.id == parentAccountID }) else { continue // 避免强制解包崩溃 } if account.visible { accountsCopy[parentIndex].childrenAccounts.append(account) if account.accounting { let parentRate = ratesMap[accountsCopy[parentIndex].currency] ?? 1 let accountRate = ratesMap[account.currency] ?? 1 let relation = parentRate / accountRate accountsCopy[parentIndex].budget += account.budget * relation accountsCopy[parentIndex].remainder += account.remainder * relation } } accountsCopy[i].isChild = true } } return accountsCopy }
解决方案2:避免重复计算groupedAccounts
当前groupedAccounts是计算属性,每次访问都会重新调用groupAccounts,加上原数据变更触发@Query更新,会导致无限循环。可以用以下方式缓存结果:
方式1:使用@Memoized(iOS17+/macOS14+)
struct AccountCircleList: View { @Query var accounts: [Account] @Query var currencies: [Currency] // 仅当accounts/currencies变更时重新计算 @Memoized var groupedAccounts: [Account] { groupAccounts(accounts: accounts, currencies: currencies) } ... }
方式2:手动缓存(兼容旧系统)
struct AccountCircleList: View { @Query var accounts: [Account] @Query var currencies: [Currency] @State private var cachedGroupedAccounts: [Account] = [] var body: some View { // 你的视图内容 } .onChange(of: [accounts, currencies]) { _, _ in cachedGroupedAccounts = groupAccounts(accounts: accounts, currencies: currencies) } .onAppear { cachedGroupedAccounts = groupAccounts(accounts: accounts, currencies: currencies) } }
附加问题:无需直接传入汇率货币的方案
利用SwiftData的ModelContext直接查询所需汇率,不用传递整个currencies数组:
// 注入ModelContext到函数 func groupAccounts(accounts: [Account], context: ModelContext) -> [Account] { var accountsCopy = accounts.map { $0.deepCopy() } // 仅查询用到的币种,提升效率 let usedCurrencies = Set(accounts.flatMap { [$0.currency] }) let fetchDescriptor = FetchDescriptor<Currency>( predicate: #Predicate { usedCurrencies.contains($0.isoCode) } ) guard let currencies = try? context.fetch(fetchDescriptor) else { return accountsCopy } let ratesMap = Dictionary(uniqueKeysWithValues: currencies.map { ($0.isoCode, $0.rate) }) // 后续逻辑不变... return accountsCopy }
调用时传入视图的modelContext:
struct AccountCircleList: View { @Query var accounts: [Account] @Environment(\.modelContext) private var context @Memoized var groupedAccounts: [Account] { groupAccounts(accounts: accounts, context: context) } ... }
内容的提问来源于stack exchange,提问作者bonavii
相关产品推荐
相关产品推荐

