Open3
SwiftData: .unique属性とはなんだ?

SwiftDataで、@Attribute(.unique)
をつけた場合の動作が思ってたのと違ってて混乱しています。
↓のような感じでデータクラスを用意します。
import SwiftData
@Model
class TestData {
@Attribute(.unique) var name: String
@Relationship(inverse: \TestValue.parent) var values: [TestValue]
init(name: String, values: [TestValue]) {
self.name = name
self.values = values
}
}
@Model
class TestValue {
var value: Int
var parent: TestData? = nil
init(value: Int) {
self.value = value
}
}
TestData
のname
はユニーク属性が付けてあるので、追加なのか保存なのか、そういうタイミングで一意性のチェックがかかって保存できないのだろう、どこかでthrow
されてくるのだろう、と思ってたんですね。
do {
let container = try ModelContainer(for: TestData.self, TestValue.self)
let context = ModelContext(container)
try context.delete(model: TestData.self)
try context.delete(model: TestValue.self)
let value1 = TestValue(value: 1)
let value2 = TestValue(value: 2)
let value3 = TestValue(value: 3)
let value4 = TestValue(value: 4)
context.insert(value1)
context.insert(value2)
context.insert(value3)
context.insert(value4)
let data1 = TestData(name: "a", values: [value1])
let data2 = TestData(name: "b", values: [value2])
let data3 = TestData(name: "c", values: [value3])
let data4 = TestData(name: "a", values: [value4])
context.insert(data1)
context.insert(data2)
context.insert(data3)
// do { // ↓ ※これ
// try context.save()
// }
// catch {
// print("error3: \(error.localizedDescription)")
// }
context.insert(data4)
do {
try context.save()
}
catch {
print("error2: \(error.localizedDescription)")
}
let fetchDesc = FetchDescriptor<TestData>(sortBy: [.init(\.name)])
let results = try context.fetch(fetchDesc)
for result in results {
print("data.name: \(result.name), data.values: \(result.values.map(\.value))")
}
}
catch {
print("error1: \(error.localizedDescription)")
}
コードの内容としては、最初に既存データを削除して、TestValues
の1〜4を追加、その後、TestData
を"a"
→"b"
→"c"
→"a"
の順で追加します。"a"
がダブりです。
実際、このコードを実行してみると、name="a"
のTestData
レコードは確かに一つなんですが、エラーも起きずにvalues
に2つの値が入ってしまう(=マージされてしまう)のです。
data.name: a, data.values: [1, 4]
data.name: b, data.values: [2]
data.name: c, data.values: [3]
ちなみにコメントアウトしてある※
の箇所のsave()
がないと、
のようなメッセージがログに出ます。「保存する前の一時データ内でユニーク識別子がダブってるよ!」みたいなことなんでしょうかね。
(※
の箇所でsave()
すると出なくなります)
マージされるのはかなりキモい動きのような気がしますね。
Xcode 16.1/macOS 15.1.1です。

iOS 18/macOS 15から入ったマクロ#Unique
でも同じですね。
@Model
class TestData {
//@Attribute(.unique) var name: String
#Unique<TestData>([\.name])
var name: String
@Relationship(inverse: \TestValue.parent) var values: [TestValue]
init(name: String, values: [TestValue]) {
self.name = name
self.values = values
}
}
そういうもんなんでしょうか。

ちなみにinverse
をなくすと、2個目の"a"
を追加後save()
した段階で、fatalError
により止まります。
@Model
class TestData {
//@Attribute(.unique) var name: String
#Unique<TestData>([\.name])
var name: String
//@Relationship(inverse: \TestValue.parent) var values: [TestValue]
var values: [TestValue]
init(name: String, values: [TestValue]) {
self.name = name
self.values = values
}
}
@Model
class TestValue {
var value: Int
//var parent: TestData? = nil
init(value: Int) {
self.value = value
}
}