SwiftDataを使ったアプリ開発をしていると、次のようなエラーに遭遇することがあります。
Thread 1: "NSFetchRequest could not locate an NSEntityDescription for entity name 'MyList'"
このエラーは、iOS 17以降で導入された新しいデータ永続化フレームワーク「SwiftData」を学ぶ際に、初心者が遭遇しやすい典型的なエラーです。SwiftDataは内部的にCore Dataを使用しているため、このようなCore Data由来のエラーメッセージが表示されることがあります。
本記事では、このエラーの意味と具体的な解決方法を、Swift初心者の方にもわかりやすく解説します。
エラーの意味を理解しよう
このエラーメッセージは、「MyListという名前のエンティティ(モデル)が見つかりません」という意味です。
SwiftDataは、裏側でCore Dataを使用してデータを管理しています。そのため、@Model
マクロで定義したクラスが正しくCore Dataのエンティティとして認識されていない場合、このエラーが発生します。
エラーが発生する典型的な状況
@Model
マクロが正しく適用されていないModelContainer
にモデルが登録されていないSchema
を使用している場合、モデルが含まれていない- データが一度も保存されていない
- ビルドキャッシュの問題でモデルが認識されていない
よくあるコード例
import SwiftData
import SwiftUI
@Model
class MyList {
var name: String
var items: [String]
init(name: String, items: [String] = []) {
self.name = name
self.items = items
}
}
struct ContentView: View {
@Query var lists: [MyList] // ここでエラーが発生
var body: some View {
List(lists, id: \.name) { list in
Text(list.name)
}
}
}
エラーの原因と解決方法
1. @Modelマクロの付け忘れ・設定ミス
SwiftDataでは、データモデルとして使用するクラスに必ず@Model
マクロを付ける必要があります。
解決方法
// ❌ 間違い:@Modelマクロがない
class MyList {
var name: String
init(name: String) {
self.name = name
}
}
// ✅ 正しい:@Modelマクロを追加
import SwiftData
@Model
class MyList {
var name: String
init(name: String) {
self.name = name
}
}
アクセス修飾子がある場合は、@Model
を先に書きます。
@Model
public class MyList {
var name: String
init(name: String) {
self.name = name
}
}
2. ModelContainerにモデルが登録されていない
アプリの起動時にModelContainer
を作成し、使用するモデルを明示的に登録する必要があります。
解決方法
import SwiftUI
import SwiftData
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: MyList.self)
}
}
複数のモデルを使用する場合は、配列で指定します。
.modelContainer(for: [MyList.self, MyItem.self, Category.self])
3. Schemaにモデルが含まれていない
Schema
を明示的に定義している場合、そのSchemaに含まれていないモデルはエンティティとして認識されません。マイグレーションやバージョン管理を行う際に発生しやすいエラーです。
問題のあるコード例
@main
struct MyApp: App {
let container: ModelContainer
init() {
let schema = Schema([
MyItem.self,
Category.self
// MyList.selfが含まれていない!
])
let configuration = ModelConfiguration(schema: schema)
container = try! ModelContainer(for: schema, configurations: configuration)
}
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(container)
}
}
}
解決方法
let schema = Schema([
MyList.self, // 追加!
MyItem.self,
Category.self
])
バージョン管理時の例:
enum SchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] {
[MyItem.self, Category.self, MyList.self] // すべてのモデルを含める
}
}
4. SwiftDataのインポート忘れ
モデルファイルとビューファイルの両方でimport SwiftData
が必要です。
解決方法
// モデルファイル(MyList.swift)
import SwiftData
import Foundation
@Model
class MyList {
var name: String
init(name: String) {
self.name = name
}
}
// ビューファイル(ContentView.swift)
import SwiftUI
import SwiftData // ← 忘れずに
struct ContentView: View {
@Query var lists: [MyList]
var body: some View {
List(lists, id: \.name) { list in
Text(list.name)
}
}
}
5. データが一度も保存されていない
これは見落としがちな重要な原因です。 モデルの定義が正しくても、そのモデルのデータが一度も保存されていない場合、Core Dataのスキーマが生成されず、エラーが発生することがあります。
特に以下のタイミングで発生します:
- アプリの初回起動時
- シミュレータやアプリをリセットした直後
- データベースファイルが削除された後
解決方法
初回起動時に自動的にデータを作成するか、空の状態を考慮したUIを作成します。
@main
struct MyApp: App {
let container: ModelContainer
init() {
do {
container = try ModelContainer(for: MyList.self)
initializeDataIfNeeded(container: container)
} catch {
fatalError("ModelContainerの初期化に失敗: \(error)")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
private func initializeDataIfNeeded(container: ModelContainer) {
let context = container.mainContext
let descriptor = FetchDescriptor<MyList>()
do {
let existingLists = try context.fetch(descriptor)
if existingLists.isEmpty {
let sampleList = MyList(name: "はじめてのリスト", items: [])
context.insert(sampleList)
try context.save()
}
} catch {
print("データ確認エラー: \(error)")
}
}
}
空の状態を考慮したUI:
struct ContentView: View {
@Query private var lists: [MyList]
@Environment(\.modelContext) private var modelContext
var body: some View {
Group {
if lists.isEmpty {
VStack {
Text("リストがありません")
.font(.title2)
.foregroundColor(.secondary)
Button("最初のリストを作成") {
createFirstList()
}
.buttonStyle(.borderedProminent)
}
} else {
List(lists, id: \.name) { list in
Text(list.name)
}
}
}
}
private func createFirstList() {
let newList = MyList(name: "新しいリスト", items: [])
modelContext.insert(newList)
try? modelContext.save()
}
}
6. Xcodeのビルドキャッシュの問題
Xcodeのビルドキャッシュが原因で、最新のモデル定義が反映されていない場合があります。
解決方法
以下の手順を順番に試してください。
- Clean Buildを実行
- Xcodeのメニューから「Product」→「Clean Build Folder」(Shift + Command + K)
- プロジェクトを再ビルド(Command + B)
- Derived Dataの削除
- Xcodeのメニューから「Xcode」→「Settings」
- 「Locations」タブを選択
- 「Derived Data」の矢印アイコンをクリックしてフォルダを開く
- 該当するプロジェクトのフォルダを削除
- Xcodeを再起動して再ビルド
- シミュレータのリセット
- シミュレータのメニューから「Device」→「Erase All Content and Settings」
- 実機の場合はアプリを削除して再インストール
7. モデルクラスの定義に問題がある
SwiftDataの@Model
マクロは、特定の条件を満たすクラスにのみ適用できます。
確認すべきポイント
- クラスは
class
として定義(struct
は使用不可) - プロパティは
var
で定義(let
は使用不可) - サポートされている型のみを使用
解決方法
// ❌ 間違い:structを使用
@Model
struct MyList {
var name: String
}
// ✅ 正しい:classを使用
@Model
class MyList {
var name: String
init(name: String) {
self.name = name
}
}
// ❌ 間違い:letで定義
@Model
class MyList {
let name: String
init(name: String) {
self.name = name
}
}
// ✅ 正しい:varで定義
@Model
class MyList {
var name: String
init(name: String) {
self.name = name
}
}
サポートされているプロパティ型
- 基本型:
String
,Int
,Double
,Bool
,Date
- オプショナル型:
String?
,Int?
など - コレクション:
Array
,Set
- 他の
@Model
クラスへの参照 Data
,UUID
enum
(RawRepresentableな型)
エラーハンドリングの実装
本番環境での予期せぬエラーに備えて、適切なエラーハンドリングを実装しましょう。
基本的なエラーハンドリング
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var lists: [MyList]
@State private var errorMessage: String?
var body: some View {
Group {
if let errorMessage = errorMessage {
VStack {
Image(systemName: "exclamationmark.triangle")
.font(.largeTitle)
.foregroundColor(.red)
Text(errorMessage)
.padding()
}
} else {
List(lists, id: \.name) { list in
Text(list.name)
}
}
}
.onAppear {
validateModelSetup()
}
}
func validateModelSetup() {
do {
let descriptor = FetchDescriptor<MyList>()
let _ = try modelContext.fetch(descriptor)
} catch {
errorMessage = "データの読み込みに失敗しました"
print("Error: \(error)")
}
}
}
ModelContainer初期化時のエラーハンドリング
@main
struct MyApp: App {
@State private var container: ModelContainer?
@State private var initError: Error?
init() {
setupModelContainer()
}
var body: some Scene {
WindowGroup {
if let container = container {
ContentView()
.modelContainer(container)
} else if let error = initError {
ErrorView(error: error)
} else {
ProgressView("データを読み込み中...")
}
}
}
private func setupModelContainer() {
do {
container = try ModelContainer(for: MyList.self)
} catch {
initError = error
}
}
}
struct ErrorView: View {
let error: Error
var body: some View {
VStack(spacing: 20) {
Image(systemName: "exclamationmark.octagon.fill")
.font(.system(size: 60))
.foregroundColor(.red)
Text("データベースの初期化に失敗しました")
.font(.headline)
Text(error.localizedDescription)
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding()
}
.padding()
}
}
エラーを防ぐためのベストプラクティス
1. モデルの定義を一箇所にまとめる
MyApp/
├── Models/
│ ├── MyList.swift
│ ├── MyItem.swift
│ └── Category.swift
├── Views/
│ └── ContentView.swift
└── MyApp.swift
2. @Modelマクロの使用を徹底する
import SwiftData
@Model
final class MyList {
var name: String
var createdAt: Date
var items: [MyItem]
init(name: String, items: [MyItem] = []) {
self.name = name
self.createdAt = Date()
self.items = items
}
}
3. ModelContainerの設定を確実に行う
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [MyList.self, MyItem.self, Category.self])
}
}
4. プレビュー用のModelContainerを用意する
extension ModelContainer {
static var preview: ModelContainer {
let container = try! ModelContainer(
for: MyList.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
let sample1 = MyList(name: "買い物リスト", items: [])
let sample2 = MyList(name: "TODO", items: [])
container.mainContext.insert(sample1)
container.mainContext.insert(sample2)
return container
}
}
#Preview {
ContentView()
.modelContainer(ModelContainer.preview)
}
5. ビルド設定を確認する
- Deployment Target: iOS 17.0以上
- Swift Language Version: Swift 5.9以上
トラブルシューティングチェックリスト
エラーが解決しない場合は、以下を順番に確認してください。
基本設定の確認
- [ ] モデルクラスに
@Model
マクロが付いているか - [ ] モデルクラスが
class
として定義されているか - [ ] すべてのプロパティが
var
で定義されているか - [ ]
import SwiftData
が記述されているか(モデルとビューの両方) - [ ]
ModelContainer
が正しく設定されているか - [ ] 使用するすべてのモデルが
ModelContainer
に登録されているか - [ ] Schemaを使用している場合、すべてのモデルが含まれているか
- [ ] iOS Deployment Targetが17.0以上に設定されているか
データの確認
- [ ] アプリで一度もデータを保存していないか
- [ ] 最近アプリやシミュレータをリセットしたか
- [ ] 初回起動時にサンプルデータを作成する処理があるか
- [ ] 空のデータ状態を想定したUI設計になっているか
ビルド・キャッシュの確認
- [ ] Clean Build Folderを実行したか
- [ ] Derived Dataを削除したか
- [ ] Xcodeを再起動したか
- [ ] シミュレータ/実機でアプリを削除して再インストールしたか
SwiftDataとCore Dataの違い
SwiftDataは内部的にCore Dataを使用していますが、より現代的で使いやすいAPIを提供しています。
項目 | Core Data | SwiftData |
---|---|---|
データモデル定義 | .xcdatamodeldファイル | @Model マクロを使ったSwiftコード |
最小iOS | iOS 3.0〜 | iOS 17.0〜 |
マクロの使用 | なし | @Model , @Query など |
学習曲線 | やや急 | 緩やか |
このため、SwiftDataを使っていても、エラーメッセージにはCore Data由来の用語が表示されることがあります。これは正常な動作です。
まとめ
SwiftDataで「NSFetchRequest could not locate an NSEntityDescription for entity name」エラーに遭遇したら、以下のポイントを確認しましょう。
- @Modelマクロの確認 – すべてのモデルクラスに付いているか
- ModelContainerの設定 – アプリ起動時に正しく登録されているか
- Schemaの確認 – 明示的にSchemaを使用している場合、すべてのモデルが含まれているか
- import文の確認 – SwiftDataがインポートされているか
- データの存在確認 – 初回起動時やリセット後は、データが存在しないことが原因の可能性が高い
- ビルドキャッシュのクリア – Clean BuildやDerived Dataの削除
- モデルの定義 – classを使い、プロパティはvarで定義
特に見落としがちなのが以下の2点です:
Schemaを使用している場合: マイグレーションやバージョン管理でSchemaを明示的に定義していると、そこに含まれていないモデルはエンティティとして認識されません。
データが一度も保存されていない場合: モデルの定義が完璧でも、実際のデータが存在しなければエラーが発生します。初回起動時のサンプルデータ作成や、空の状態を考慮したUI設計を心がけましょう。
SwiftDataはiOS 17以降の新しい技術ですが、適切な設定を行えば、Core Dataよりもシンプルにデータ永続化を実装できます。エラーメッセージを恐れずに、一つずつ確認していけば必ず解決できます。