在 SwiftData 中,依赖实例和父实例的插入顺序是什么?在下面的代码片段中,当依赖实例插入到父实例之前时,它会起作用。但是,当顺序颠倒时,它会失败。为什么会这样?
import Foundation
import SwiftData
import SwiftUI
struct DebugView: View {
@Query var parent: [ParentClass]
@Query var dept: [DependentClass]
var body: some View {
Text("parent cnt:\(parent.count), dept cnt:\(dept.count)")
}
}
@Model
class ParentClass {
@Relationship(deleteRule: .cascade)
var deps: DependentClass
init(deps: DependentClass) {
self.deps = deps
}
}
@Model
class DependentClass {
var id: String
init(id: String = UUID().uuidString) {
self.id = id
}
}
#Preview {
do {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: ParentClass.self, configurations: config)
let context = ModelContext(container)
var dept = DependentClass()
var parentClass = ParentClass(deps: dept)
/* ok to insert dept before parent. View shows: "parent cnt:1, dept cnt:1" */
context.insert(dept)
context.insert(parentClass)
/* ok to insert only parent, swiftData will insert dept too. View shows: "parent cnt:1, dept cnt:1" */
// context.insert(parentClass)
/* failed to insert dept after parent, error list below */
// context.insert(parentClass)
// context.insert(dept)
return DebugView()
.modelContainer(container)
} catch {
return Text("Failed to create container: \(error.localizedDescription)")
}
}
错误日志插入父级之后的部门:
Date/Time: 2024-01-13 14:10:10.3694 +0800
Launch Time: 2024-01-13 14:10:09.9932 +0800
OS Version: macOS 14.1.1 (23B81)
Release Type: User
Report Version: 104
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001929f3938
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Terminating Process: exc handler [51868]
Triggered by Thread: 0
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libswiftCore.dylib 0x1929f3938 _assertionFailure(_:_:file:line:flags:) + 248
1 SwiftData 0x1c42fc454 0x1c42d1000 + 177236
2 SwiftData 0x1c4315aac 0x1c42d1000 + 281260
3 SwiftData 0x1c42fd4e0 0x1c42d1000 + 181472
4 DebugView.1.preview-thunk.dylib 0x10161ae10 closure #1 in static $s39gogodict_PreviewReplacement_DebugView_133_20B25209EACDFA98A88DFB5B90B26E4CLl0B0fMf_15PreviewRegistryfMu_.makePreview() + 840 (@__swiftmacro_39gogodict_PreviewReplacement_DebugView_133_20B25209EACDFA98A88DFB5B90B26E4CLl0B0fMf_.swift:18)
5 PreviewsInjection 0x1d6aaa0ec 0x1d6a6f000 + 241900
6 PreviewsInjection 0x1d6aab060 0x1d6a6f000 + 245856
7 libswift_Concurrency.dylib 0x1e450b738 static MainActor.assumeIsolated<A>(_:file:line:) + 144
8 PreviewsInjection 0x1d6aa9e48 0x1d6a6f000 + 241224
9 PreviewsInjection 0x1d6aadfb8 0x1d6a6f000 + 257976
10 PreviewsInjection 0x1d6aaea88 0x1d6a6f000 + 260744
11 PreviewsInjection 0x1d6a9c89c 0x1d6a6f000 + 186524
12 PreviewsInjection 0x1d6a9f5c8 0x1d6a6f000 + 198088
13 PreviewsInjection 0x1d6a87c90 0x1d6a6f000 + 101520
14 PreviewsInjection 0x1d6a88128 0x1d6a6f000 + 102696
15 PreviewsInjection 0x1d6aa0fa0 0x1d6a6f000 + 204704
16 PreviewsInjection 0x1d6a777b8 0x1d6a6f000 + 34744
17 PreviewsInjection 0x1d6a77004 0x1d6a6f000 + 32772
18 PreviewsFoundation 0x1d69b4630 0x1d691c000 + 624176
19 libdispatch.dylib 0x18016b4f4 _dispatch_call_block_and_release + 24
20 libdispatch.dylib 0x18016cd3c _dispatch_client_callout + 16
21 libdispatch.dylib 0x18017bb24 _dispatch_main_queue_drain + 1272
22 libdispatch.dylib 0x18017b61c _dispatch_main_queue_callback_4CF + 40
23 CoreFoundation 0x1803f1a30 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
24 CoreFoundation 0x1803ec148 __CFRunLoopRun + 1936
25 CoreFoundation 0x1803eb5a4 CFRunLoopRunSpecific + 572
26 GraphicsServices 0x18e9fbae4 GSEventRunModal + 160
27 UIKitCore 0x1852f02e4 -[UIApplication _run] + 868
28 UIKitCore 0x1852f3f5c UIApplicationMain + 124
29 SwiftUI 0x1c51fc1b0 0x1c4371000 + 15249840
30 SwiftUI 0x1c51fc050 0x1c4371000 + 15249488
31 SwiftUI 0x1c4f02fa4 0x1c4371000 + 12132260
32 gogodict 0x1009f03b0 static GOGODictApp.$main() + 40
33 gogodict 0x1009f0474 main + 12 (GOGODictApp.swift:12)
34 dyld_sim 0x100bfd544 start_sim + 20
35 dyld 0x100ce60e0 start + 2360
对于像我这样的新手来说,这似乎很令人困惑。有没有解释引擎底层逻辑的文档或者文章?
当您为对象分配关系属性时,SwiftData 将根据需要为您处理插入当前 ModelContext 实例的操作。
所以当你有像 ParentClass 这样的 init 时
init(deps: DependentClass) {
self.deps = deps
}
然后 DependentClass 对象(从这里开始我将它们称为父对象和依赖对象)将被插入到与父对象相同的上下文中(如果已插入)。
问题是你(目前)永远不能将同一个对象两次插入到同一个上下文中,否则你会崩溃。希望这个问题很快就能得到解决,这样 SwiftData 就会忽略第二次插入,或者更好的是我们会得到一个编译时错误,但我不确定这是否可能。
所以要了解问题中的场景:
先插入依赖对象
context.insert(dept)
context.insert(parentClass)
这里,当执行父 init 时,依赖项已经插入到上下文中,并且 SwiftData 正确地确认了这一点,并且不会尝试插入依赖项。这确实不足为奇,因为这是一个非常常见的用例,关系的一侧已经存在于上下文中。
切勿插入依赖对象
context.insert(parentClass)
这已经在前面描述过,当插入父级时,SwiftData 会处理从属级的插入。 请注意,如果您有一对多关系,在连接两个对象之前必须先将对象插入到上下文中,这也可能会导致一些问题
最后插入依赖对象
context.insert(parentClass)
context.insert(dept)
这是崩溃场景,因为 SwiftData 已经将依赖对象插入到上下文中,最后一次插入将导致崩溃。
总而言之,顺序很重要,特别是对于多对多关系,但导致崩溃的原因是一个对象被多次插入到模型上下文中。