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는 지정된 키 경로에 따라 결과를 섹션으로 그룹화하며, sectionNameKeyPath가 있는 UITableView와 유사합니다. 섹션 헤더가 있는 그룹화된 목록에는 @SectionedFetchRequest를 사용하세요.
관계별로 필터링하려면 점 표기법을 사용하세요: NSPredicate(format: “category.name == %@”, “Work”). @FetchRequest는 SQLite에서 자동으로 JOIN을 수행합니다. 선택한 객체를 통해 관계에 액세스하려면 일반 NSManagedObject 속성을 사용하세요.
@FetchRequest는 반응형 업데이트로 모든 레코드를 표시하도록 설계되었기 때문에 직접 페이지네이션을 지원하지 않습니다. 페이지네이션의 경우 fetchLimit/fetchOffset과 함께 NSFetchRequest를 사용하고 목록 스크롤 시 수동으로 데이터를 로드하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.