SwiftUIの@FetchRequestとは — Core Dataのフェッチとメカニズム

著者: IT Sectr 公開日: 2026-06-25 読了時間: 8 分

SwiftUIの@FetchRequest — Core Dataからデータを自動的にフェッチし、リアクティブにUIを更新するためのproperty wrapperです。Wrapperはフィルタ述語とソート記述子を指定したNSFetchRequestを受け取り、結果 — NSManagedObjectの配列 — はCore Dataコンテキストの変更に応じて自動的に更新されます。Apple Developer Documentation (2025)によると、@FetchRequestはSwiftUI環境でmanagedObjectContextを必要とし、動的述語をサポートしており、Core Dataとユーザーインターフェースの間のリアクティブな接続を提供します。

重要なポイント

  • @FetchRequest — SwiftUIでリアクティブにCore Dataをフェッチするためのproperty wrapper
  • 自動更新 — Core Dataでレコードが追加、削除、変更されるとUIが自動更新される
  • 述語とソート記述子は初期化子またはproperty wrapperパラメーターで設定
  • managedObjectContextはView階層で@Environmentを介して利用可能にする必要がある
  • 動的述語により実行時にフィルター条件を変更可能

SwiftUIの@FetchRequestとは?

@FetchRequestは、Core DataからFetchRequestを作成および管理するSwiftUIのproperty wrapperです。初期化時に、wrapperはエンティティタイプ、ソート用のNSSortDescriptor配列、およびオプションのフィルタリング用NSPredicateを受け取ります。結果は指定されたNSManagedObjectタイプのオブジェクト配列として保存され、永続化層で変更が発生すると自動的に更新されます。

@FetchRequestの主な特徴はリアクティビティです。Core Dataのデータが変更されると(save、insert、delete、update)、SwiftUIは自動的に再フェッチを実行し、このフェッチリクエストを使用するすべてのViewを再描画します。開発者は手動でrefreshを呼び出したり、NSManagedObjectContextDidSaveNotificationを購読したり、データをリロードする必要はありません。SwiftUIが同期サイクル全体を管理します。

@FetchRequestを機能させるには、@Environment(\.managedObjectContext)を介してSwiftUI環境でNSManagedObjectContextが利用可能である必要があります。コンテキストは通常、NSPersistentContainerが作成されるアプリケーションルートから渡されます。標準的なパターンは、ViewModelまたはApp structを使用してCore Dataスタックを構成し、.environment()修飾子を介してコンテキストを渡すことです。

FetchRequestのソートと述語

@FetchRequestはフェッチを構成するためのいくつかのパラメーターを受け取ります。SortDescriptors — レコードの順序を決定するNSSortDescriptorの配列。Predicate — フィルタリング用のNSPredicate。例えば、ステータスが「完了」のタスクのみをフェッチします。Animation — データ変更時のリスト更新アニメーション。

Core Dataの述語はSQLに似た式の形式を使用します:NSPredicate(format: “status == %@”, “completed”)。AND、OR、NOTを使用して複数の条件を組み合わせることができます。SortDescriptorsは順序を定義します:NSSortDescriptor(keyPath: \Task.dueDate, ascending: true)。複数のフィールドでソートするには、記述子の配列を渡します — 最初のものが主キーとして適用され、残りが副キーとして適用されます。

swift
struct TaskListView: View {
    @Environment(\.managedObjectContext) var viewContext

    @FetchRequest(
        sortDescriptors: [
            NSSortDescriptor(keyPath: \.Task.dueDate, ascending: true),
            NSSortDescriptor(keyPath: \.Task.priority, ascending: false)
        ],
        predicate: NSPredicate(format: "isCompleted == NO"),
        animation: .default
    )
    var tasks: FetchedResults<Task>

    var body: some View {
        List(tasks) { task in
            Text(task.title ?? "")
        }
    }
}

この例では、@FetchRequestはすべての未完了タスク(isCompleted == NO)をフェッチし、最初に日付(昇順)、次に優先度(降順)でソートします。FetchedResults<Task>は自動更新されるコレクションで、ForEachやListに対して配列のように動作します。.defaultアニメーションにより、変更時のレコードのスムーズな表示/非表示が保証されます。

Listと動的データでの@FetchRequest

@FetchRequestは動的なCore Dataリストを表示するためにListやForEachと完全に連携します。フェッチ結果(FetchedResults)はRandomAccessCollectionプロトコルに準拠しているため、ListやForEachで直接使用できます。レコードが追加、削除、変更されると、リストは自動的にアニメーション付きで再描画されます。

レコードを削除するには、コンテキストメソッドdeleteを使用します。レコードを追加するには、新しいNSManagedObjectを作成してsaveを呼び出します。すべての変更はviewContextを介して実行され、@FetchRequestとともにデータの整合性を保証します。編集には、特定のCore Dataオブジェクトに対して@ObservedObjectまたは@Bindableを使用します。

swift
struct TaskListWithActions: View {
    @Environment(\.managedObjectContext) var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \.Task.createdAt,
                                               ascending: false)]
    )
    var tasks: FetchedResults<Task>

    var body: some View {
        List {
            ForEach(tasks) { task in
                HStack {
                    Text(task.title ?? "")
                    Spacer()
                    Image(systemName: task.isCompleted
                               ? "checkmark.circle.fill"
                               : "circle")
                }
                .onTapGesture { toggleTask(task) }
            }
            .onDelete(perform: deleteTasks)
        }
        .toolbar {
            Button(systemImage: "plus") { addTask() }
        }
    }

    private func addTask() {
        let newTask = Task(context: viewContext)
        newTask.title = "New task \(tasks.count + 1)"
        newTask.createdAt = Date()
        try? viewContext.save()
    }

    private func toggleTask(_ task: Task) {
        task.isCompleted.toggle()
        try? viewContext.save()
    }

    private func deleteTasks(at offsets: IndexSet) {
        for index in offsets {
            viewContext.delete(tasks[index])
        }
        try? viewContext.save()
    }
}

この例は、@FetchRequestを使用した完全なCRUDを示しています。追加:コンテキストを介してTaskを作成し、save()。削除:コンテキストでdelete()し、save()。更新:プロパティをトグルしてsave()。各save()の後、@FetchRequestは自動的にデータを再フェッチし、Listはアニメーション付きで更新されます。onDeleteおよびonTapGesture修飾子は標準のiOSインタラクションを提供します。

@FetchRequestのコード例

レコードをフェッチするための@FetchRequestを使用した完全な日記アプリケーションの例を見てみましょう。検索フォームは動的述語を使用します:テキストを入力すると、FetchRequestが自動的に結果を更新し、タイトルとコンテンツでレコードをフィルタリングします。ソートは作成日順で、新しいものから古いものへ。

swift
struct JournalEntryView: View {
    @Environment(\.managedObjectContext) var viewContext
    @State private var searchText = ""

    private var searchPredicate: NSPredicate? {
        guard !searchText.isEmpty else { return nil }
        return NSPredicate(format: "title CONTAINS[c] %@", searchText)
    }

    @FetchRequest var entries: FetchedResults<JournalEntry>

    init(searchText: String) {
        let sort = [NSSortDescriptor(keyPath: \.JournalEntry.date,
                                           ascending: false)]
        let pred: NSPredicate? = searchText.isEmpty
            ? nil
            : NSPredicate(format: "title CONTAINS[c] %@ OR content CONTAINS[c] %@",
                          searchText, searchText)
        self._entries = FetchRequest(
            sortDescriptors: sort,
            predicate: pred,
            animation: .default
        )
    }

    var body: some View {
        List(entries) { entry in
            VStack(alignment: .leading) {
                Text(entry.title ?? "").font(.headline)
                Text(entry.content ?? "").font(.subheadline)
                    .lineLimit(2)
            }
        }
        .searchable(text: $searchText)
    }
}

検索機能付き日記:searchableフィールドにテキストを入力すると、titleフィールドとcontentフィールドに対してCONTAINS[c](大文字小文字を区別しない検索)を使用したNSPredicateが作成されます。InitはsearchTextを受け取り、対応する述語を持つFetchRequestを作成します。searchable修飾子は自動的にsearchTextを更新し、@FetchRequestは述語の変更に反応してデータを再フェッチします。

複数のフィルターを使用するより複雑なシナリオでは、AND/ORロジックでNSCompoundPredicateを組み合わせます。@FetchRequestは、リレーションシップによるフィルタリングのためのネストされたサブクエリ(SUBQUERY)を含む、任意の複雑さの述語をサポートします。ただし、複雑な述語はパフォーマンスに影響することに注意してください — 大規模なデータセット(10,000レコード以上)の場合は、Core Dataのインデックスを使用し、fetchLimitでフェッチを制限してください。

実行時の動的述語

@FetchRequestは、実行時に変更可能なpredicateパラメーターを介して動的述語をサポートします。これを行うには、変数述語を持つ@FetchRequestを作成し、Bindingを介して渡します。ユーザーがフィルターを変更すると — 例えば、タスクカテゴリを選択すると — 述語が更新され、フェッチが自動的に再開されます。

動的述語は、@FetchRequestの初期化子にBindingとして渡される@Stateまたは@Publishedプロパティを介して実装されます。SwiftUIはこのBindingの変更を追跡し、新しい述語でNSFetchRequestを再作成し、自動的にリストを更新します。

よくある質問

@FetchRequestにmanagedObjectContextを渡すには?

ルートViewに@Environment(\.managedObjectContext) var viewContextを追加し、.environment(\.managedObjectContext, context)修飾子を介して渡します。@FetchRequestを使用するすべての子Viewは、環境から自動的にコンテキストを受け取ります。

Viewをリロードせずに@FetchRequestを更新できますか?

はい、Core Dataコンテキストを変更します:オブジェクトを追加、削除、または更新してsave()を呼び出します。@FetchRequestは自動的に再フェッチを実行し、UIを更新します。save()なしで強制更新するには、viewContext.refreshAllObjects()を使用します。

@FetchRequestと@SectionedFetchRequestの違いは?

@FetchRequestはオブジェクトのフラットな配列を返します。@SectionedFetchRequestは、指定されたキーパスに従って結果をセクションにグループ化します。UITableViewのsectionNameKeyPathと同様です。セクションヘッダー付きのグループ化リストには@SectionedFetchRequestを使用します。

リレーションシップで@FetchRequestを使用するには?

リレーションシップでフィルタリングするには、ドット記法を使用します:NSPredicate(format: “category.name == %@”, “Work”)。@FetchRequestは自動的にSQLiteでJOINを実行します。選択したオブジェクトを介してリレーションシップにアクセスするには、通常のNSManagedObjectプロパティを使用します。

@FetchRequestはページネーションをサポートしていますか?

@FetchRequestは、リアクティブ更新ですべてのレコードを表示するように設計されているため、直接ページネーションをサポートしていません。ページネーションには、fetchLimit/fetchOffsetを指定したNSFetchRequestを使用し、リストのスクロール時に手動でデータをロードします。

まとめ

  • @FetchRequest — SwiftUIでCore Dataを自動的にリアクティブにフェッチするためのproperty wrapper
  • ソートはNSSortDescriptorの配列で設定、フィルタリングはNSPredicateで設定
  • 自動更新 — Core Dataコンテキストのデータ変更でUIが自動更新される
  • managedObjectContextはView階層で@Environmentを介して渡される
  • 動的述語は述語パラメーターを持つカスタムinitで実装
  • CRUD操作は自動リスト同期とともにviewContextを介して実行
  • 使用 — Core Data表示には@FetchRequest、グループ化リストには@SectionedFetchRequest

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください