@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 parameters
  • managedObjectContext должен быть доступен через @Environment в иерархии View
  • Динамические предикаты позволяют менять условия фильтрации в рантайме

Что такое @FetchRequest в SwiftUI?

@FetchRequest — это property wrapper SwiftUI, создающий и управляющий FetchRequest из Core Data. При инициализации wrapper принимает entity type, массив NSSortDescriptor для сортировки и опциональный NSPredicate для фильтрации. Результат хранится как массив объектов указанного NSManagedObject типа и автоматически обновляется при изменениях в persistence layer.

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

Для работы @FetchRequest требуется NSManagedObjectContext, доступный в окружении SwiftUI через @Environment(\.managedObjectContext). Контекст обычно передаётся из корня приложения, где создаётся NSPersistentContainer. Стандартный паттерн — использование ViewModel или App struct для настройки Core Data stack и передачи контекста через .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: "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()
    }
}

Этот пример демонстрирует полный CRUD с @FetchRequest. Добавление: создание Task через контекст и save(). Удаление: delete() на контексте и save(). Обновление: toggle свойства и 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-поле создаётся NSPredicate с CONTAINS[c] (регистронезависимый поиск) по полям title и content. Init принимает searchText и создаёт FetchRequest с соответствующим предикатом. Searchable-модификатор автоматически обновляет searchText, а @FetchRequest реагирует на изменение предиката и перезапрашивает данные.

Для более сложных сценариев с множественными фильтрами комбинируйте NSCompoundPredicate с AND/OR логикой. @FetchRequest поддерживает предикаты любой сложности, включая вложенные подзапросы (SUBQUERY) для фильтрации по relationship. Однако помните: сложные предикаты влияют на производительность — для больших наборов данных (более 10 000 записей) используйте индексацию Core Data и ограничивайте выборку через fetchLimit.

Динамические предикаты в рантайме

@FetchRequest поддерживает динамические предикаты через параметр predicate, который можно менять в рантайме. Для этого нужно создать @FetchRequest с переменной-предикатом и передать её через Binding. Когда пользователь меняет фильтр — например, выбирает категорию задач — предикат обновляется, и выборка автоматически перезапускается.

Динамические предикаты реализуются через @State или @Published свойство, которое передаётся как Binding в инициализатор @FetchRequest. SwiftUI отслеживает изменение этого Binding и пересоздаёт NSFetchRequest с новым предикатом, автоматически обновляя список.

Часто задаваемые вопросы

Как передать 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 для grouped-списков с заголовками секций.

Как использовать @FetchRequest с Relationship?

Для фильтрации по relationship используйте точечную нотацию: NSPredicate(format: "category.name == %@", "Work"). @FetchRequest автоматически выполняет JOIN в SQLite. Для доступа к relationship через выбранный объект используйте обычные свойства NSManagedObject.

Поддерживает ли @FetchRequest пагинацию?

@FetchRequest не поддерживает пагинацию напрямую, так как предназначен для отображения всех записей с реактивным обновлением. Для пагинации используйте NSFetchRequest с fetchLimit/fetchOffset и подгружайте данные вручную при прокрутке списка.

Итоги

  • @FetchRequest — property wrapper для автоматической реактивной выборки Core Data в SwiftUI
  • Сортировка задаётся массивом NSSortDescriptor, фильтрация — NSPredicate
  • Автообновление UI при любых изменениях данных в контексте Core Data
  • managedObjectContext передаётся через @Environment в иерархии View
  • Динамические предикаты реализуются через кастомный init с параметром-предикатом
  • CRUD операции выполняются через viewContext с автоматической синхронизацией списка
  • Используйте @FetchRequest для отображения Core Data, @SectionedFetchRequest — для grouped-списков

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

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также