@FetchRequest в SwiftUI — какво е, извличане на Core Data и механизъм

Автор: IT Sectr Публикувано: 2026-06-25 Време за четене: 8 мин

@FetchRequest в SwiftUI — property wrapper за автоматично извличане на данни от Core Data с реактивно актуализиране на интерфейса. Wrapper приема NSFetchRequest с предикати за филтриране и сортировки, а резултатът — масив от NSManagedObject — се актуализира автоматично при всяка промяна в контекста на Core Data. Според Apple Developer Documentation (2025), @FetchRequest изисква managedObjectContext в средата на SwiftUI и поддържа динамични предикати, осигурявайки реактивна връзка между Core Data и потребителския интерфейс.

Основни точки

  • @FetchRequest — property wrapper за реактивно извличане на Core Data в SwiftUI
  • Автоматично актуализиране на UI при добавяне, изтриване или промяна на записи в Core Data
  • Предикати и сортировки се задават в инициализатора или чрез параметри на property wrapper
  • managedObjectContext трябва да бъде достъпен чрез @Environment в йерархията на View
  • Динамични предикати позволяват промяна на условията за филтриране по време на изпълнение

Какво е @FetchRequest в SwiftUI?

@FetchRequest — е SwiftUI property wrapper, който създава и управлява FetchRequest от Core Data. При инициализация wrapper приема типа на обекта, масив от NSSortDescriptor за сортиране и опционален NSPredicate за филтриране. Резултатът се съхранява като масив от обекти от посочения тип NSManagedObject и се актуализира автоматично при промени в слоя за устойчивост.

Ключовата характеристика на @FetchRequest е реактивността. Когато данните в Core Data се променят (save, insert, delete, update), SwiftUI автоматично извършва повторно извличане и прерисува всички View, които използват този fetch request. Разработчикът не трябва ръчно да вика refresh, да се абонира за NSManagedObjectContextDidSaveNotification или да презарежда данни. SwiftUI управлява целия цикъл на синхронизация.

За работата на @FetchRequest е необходим NSManagedObjectContext, достъпен в средата на SwiftUI чрез @Environment(\.managedObjectContext). Контекстът обикновено се предава от корена на приложението, където се създава NSPersistentContainer. Стандартният модел — използване на ViewModel или App структура за конфигуриране на 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 осигурява плавно появяване/изчезване на записи при промени.

@FetchRequest с List и динамични данни

@FetchRequest се комбинира идеално с List и ForEach за показване на динамични списъци от Core Data. Резултатът от извличането (FetchedResults) отговаря на протокола RandomAccessCollection, така че може да се използва директно в List и ForEach. При добавяне, изтриване или промяна на записи, списъкът автоматично се прерисува с анимация.

За изтриване на записи се използва методът delete на контекста. За добавяне — създаване на нов NSManagedObject и извикване на save. Всички промени се извършват чрез viewContext, който заедно с @FetchRequest осигурява консистентност на данните. За редактиране се използва @ObservedObject или @Bindable за конкретен Core Data обект.

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: "добави") { 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()
    }
}

Този пример демонстрира пълен CRUD с @FetchRequest. Добавяне: създаване на Task чрез контекст и save(). Изтриване: delete() на контекста и save(). Актуализиране: toggle на свойство и save(). След всяко save() @FetchRequest автоматично извлича отново данните и List се актуализира с анимация. Модификаторите onDelete и onTapGesture осигуряват стандартни iOS взаимодействия.

Динамични предикати по време на изпълнение

@FetchRequest поддържа динамични предикати чрез параметъра predicate, който може да се променя по време на изпълнение. За целта трябва да създадете @FetchRequest с променлива-предикат и да я предадете чрез Binding. Когато потребителят промени филтъра — например избере категория задача — предикатът се актуализира и извличането автоматично се рестартира.

Динамичните предикати се реализират чрез @State или @Published свойство, което се предава като Binding на инициализатора на @FetchRequest. SwiftUI проследява промяната на това Binding и пресъздава NSFetchRequest с новия предикат, автоматично актуализирайки списъка.

Примери за код на @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 полето се създава NSPredicate с CONTAINS[c] (търсене без значение на главни/малки букви) по полетата title и content. Init приема searchText и създава FetchRequest със съответния предикат. Searchable модификаторът автоматично актуализира searchText, а @FetchRequest реагира на промяната на предиката и извлича данните отново.

За по-сложни сценарии с множество филтри комбинирайте NSCompoundPredicate с AND/OR логика. @FetchRequest поддържа предикати с всякаква сложност, включително вложени подзаявки (SUBQUERY) за филтриране по релации. Имайте предвид обаче: сложните предикати влияят на производителността — за големи набори от данни (над 10 000 записа) използвайте индексиране на Core Data и ограничете извличането чрез fetchLimit.

Често задавани въпроси

Как да предам managedObjectContext на @FetchRequest?

Добавете @Environment(\.managedObjectContext) var viewContext в кореновото View и го предайте чрез модификатора .environment(\.managedObjectContext, context). Всички дъщерни View, използващи @FetchRequest, автоматично ще получат контекста от средата.

Може ли @FetchRequest да се актуализира без презареждане на View?

Да, променете контекста на Core Data: добавете, изтрийте или актуализирайте обект и извикайте save(). @FetchRequest автоматично ще извърши повторно извличане и ще актуализира UI. За принудително актуализиране без save() използвайте viewContext.refreshAllObjects().

Каква е разликата между @FetchRequest и @SectionedFetchRequest?

@FetchRequest връща плосък масив от обекти. @SectionedFetchRequest групира резултатите по указания key path в секции, подобно на UITableView с sectionNameKeyPath. Използвайте @SectionedFetchRequest за групирани списъци със заглавия на секции.

Как да използвам @FetchRequest с Relationship?

За филтриране по релации използвайте точкова нотация: NSPredicate(format: „category.name == %@”, „Work”). @FetchRequest автоматично извършва JOIN в SQLite. За достъп до релацията чрез избрания обект използвайте обикновените свойства на NSManagedObject.

Поддържа ли @FetchRequest пагинация?

@FetchRequest не поддържа директно пагинация, тъй като е предназначен за показване на всички записи с реактивно актуализиране. За пагинация използвайте NSFetchRequest с fetchLimit/fetchOffset и зареждайте данни ръчно при скролиране на списъка.

Резюме

  • @FetchRequest — property wrapper за автоматично реактивно извличане на Core Data в SwiftUI
  • Сортиране се задава с масив NSSortDescriptor, филтриране — с NSPredicate
  • Автоматично актуализиране на UI при всяка промяна на данните в контекста на Core Data
  • managedObjectContext се предава чрез @Environment в йерархията на View
  • Динамични предикати се реализират чрез custom init с параметър предикат
  • CRUD операции се извършват чрез viewContext с автоматична синхронизация на списъка
  • Използвайте @FetchRequest за показване на Core Data, @SectionedFetchRequest — за групирани списъци

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също