待ちに待ったSwiftDataをアプリで使えないかと調べていたところ、躓きそうな箇所があったのでメモ。
問題
SwiftDataの@Model
をつけたクラスにStructやEnumを置くとエラーが発生する。
このエラーNo exact matches in call to instance method
としか言われないので原因がかなりわかりづらい。
import SwiftData
struct Location {
let latitude: Double
let longitude: Double
}
@Model
final class Home {
let location: Location // エラー!!!
init(location: Location) {
self.location = location
}
}
解決方法
プロパティに含めたいStructやEnumをCodableに準拠させる。
Structの場合
単にCodableに準拠させればOK。
struct Location: Codable {
let latitude: Double
let longitude: Double
}
@Model
final class Home {
let location: Location
init(location: Location) {
self.location = location
}
}
Enumの場合
IntでもStringでも値型なEnumをCodableに準拠させればOK。
enum ChocolateType: Int, Codable {
case white
case milk
case sweet
case bitter
}
@Model
final class Chocolate {
var name: String
var type: ChocolateType
init(name: String, type: ChocolateType) {
self.name = name
self.type = type
}
}
解説
WWDC2023のMeet SwiftDataの2分あたりでサラッと複雑な値型はCodableに準拠することで使えると話している。
実際に@Model
を付けると準拠されるPersistentModelのリファレンスから、エラーが発生しているgetValue(forKey:)
について調べると、以下のいずれかに準拠した値が取得できるとわかる。
- PersistentModel
- RelationshipCollection
- Decodable
また同様にエラーが発生するsetValue(forKey:)
にもEncodableの制約がついたものがある。
よってPersistentModelは@Model
を付けたクラスであり、RelationshipCollectionは名前からしてリレーション用だと思われるので、その他の@Model
内で定義される変数はCodableに準拠することで値を格納・取得できるようにしていると考えられる。
func getValue<Value>(forKey: KeyPath<Self, Value>) -> Value where Value : Decodable
func setValue<Value>(
forKey: KeyPath<Self, Value>,
to newValue: Value
) where Value : Encodable
以下憶測だが、これを踏まえるとSwiftDataの@Attribute(originalName:)
を使うこととプロパティ名とスキーマでの名前を分けられるが、内部ではCodableのCodingKeysを使って実現しているのではないかと考えられる。