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는 지정된 키 경로에 따라 결과를 섹션으로 그룹화하며, sectionNameKeyPath가 있는 UITableView와 유사합니다. 섹션 헤더가 있는 그룹화된 목록에는 @SectionedFetchRequest를 사용하세요.

Relationships와 함께 @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 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기