SwiftUIの@FetchRequest — Core Dataからデータを自動的にフェッチし、リアクティブにUIを更新するためのproperty wrapperです。Wrapperはフィルタ述語とソート記述子を指定したNSFetchRequestを受け取り、結果 — NSManagedObjectの配列 — はCore Dataコンテキストの変更に応じて自動的に更新されます。Apple Developer Documentation (2025)によると、@FetchRequestはSwiftUI環境でmanagedObjectContextを必要とし、動的述語をサポートしており、Core Dataとユーザーインターフェースの間のリアクティブな接続を提供します。
重要なポイント
@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はフェッチを構成するためのいくつかのパラメーターを受け取ります。SortDescriptors — レコードの順序を決定するNSSortDescriptorの配列。Predicate — フィルタリング用のNSPredicate。例えば、ステータスが「完了」のタスクのみをフェッチします。Animation — データ変更時のリスト更新アニメーション。
Core Dataの述語はSQLに似た式の形式を使用します:NSPredicate(format: “status == %@”, “completed”)。AND、OR、NOTを使用して複数の条件を組み合わせることができます。SortDescriptorsは順序を定義します:NSSortDescriptor(keyPath: \Task.dueDate, ascending: true)。複数のフィールドでソートするには、記述子の配列を渡します — 最初のものが主キーとして適用され、残りが副キーとして適用されます。
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アニメーションにより、変更時のレコードのスムーズな表示/非表示が保証されます。
@FetchRequestは動的なCore Dataリストを表示するためにListやForEachと完全に連携します。フェッチ結果(FetchedResults)はRandomAccessCollectionプロトコルに準拠しているため、ListやForEachで直接使用できます。レコードが追加、削除、変更されると、リストは自動的にアニメーション付きで再描画されます。
レコードを削除するには、コンテキストメソッドdeleteを使用します。レコードを追加するには、新しいNSManagedObjectを作成してsaveを呼び出します。すべての変更はviewContextを介して実行され、@FetchRequestとともにデータの整合性を保証します。編集には、特定のCore Dataオブジェクトに対して@ObservedObjectまたは@Bindableを使用します。
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が自動的に結果を更新し、タイトルとコンテンツでレコードをフィルタリングします。ソートは作成日順で、新しいものから古いものへ。
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を再作成し、自動的にリストを更新します。
よくある質問
ルートViewに@Environment(\.managedObjectContext) var viewContextを追加し、.environment(\.managedObjectContext, context)修飾子を介して渡します。@FetchRequestを使用するすべての子Viewは、環境から自動的にコンテキストを受け取ります。
はい、Core Dataコンテキストを変更します:オブジェクトを追加、削除、または更新してsave()を呼び出します。@FetchRequestは自動的に再フェッチを実行し、UIを更新します。save()なしで強制更新するには、viewContext.refreshAllObjects()を使用します。
@FetchRequestはオブジェクトのフラットな配列を返します。@SectionedFetchRequestは、指定されたキーパスに従って結果をセクションにグループ化します。UITableViewのsectionNameKeyPathと同様です。セクションヘッダー付きのグループ化リストには@SectionedFetchRequestを使用します。
リレーションシップでフィルタリングするには、ドット記法を使用します:NSPredicate(format: “category.name == %@”, “Work”)。@FetchRequestは自動的にSQLiteでJOINを実行します。選択したオブジェクトを介してリレーションシップにアクセスするには、通常のNSManagedObjectプロパティを使用します。
@FetchRequestは、リアクティブ更新ですべてのレコードを表示するように設計されているため、直接ページネーションをサポートしていません。ページネーションには、fetchLimit/fetchOffsetを指定したNSFetchRequestを使用し、リストのスクロール時に手動でデータをロードします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。