@FetchRequest trong SwiftUI — một property wrapper để tự động truy xuất dữ liệu từ Core Data với cập nhật giao diện phản ứng. Wrapper nhận NSFetchRequest với các vị từ lọc và sắp xếp, và kết quả — một mảng NSManagedObject — tự động cập nhật khi có bất kỳ thay đổi nào trong ngữ cảnh Core Data. Theo Apple Developer Documentation (2025), @FetchRequest yêu cầu managedObjectContext trong môi trường SwiftUI và hỗ trợ các vị từ động, cung cấp kết nối phản ứng giữa Core Data và giao diện người dùng.
Những điểm chính
@FetchRequest là một property wrapper SwiftUI tạo và quản lý FetchRequest từ Core Data. Khi khởi tạo, wrapper chấp nhận một loại thực thể, một mảng NSSortDescriptor để sắp xếp và một NSPredicate tùy chọn để lọc. Kết quả được lưu trữ dưới dạng mảng các đối tượng thuộc loại NSManagedObject được chỉ định và tự động cập nhật khi có thay đổi trong lớp lưu trữ.
Tính năng chính của @FetchRequest là khả năng phản ứng. Khi dữ liệu trong Core Data thay đổi (save, insert, delete, update), SwiftUI tự động thực hiện truy xuất lại và vẽ lại tất cả các View sử dụng fetch request này. Nhà phát triển không cần gọi thủ công refresh, đăng ký NSManagedObjectContextDidSaveNotification hoặc tải lại dữ liệu. SwiftUI quản lý toàn bộ chu kỳ đồng bộ hóa.
Để @FetchRequest hoạt động, cần có NSManagedObjectContext trong môi trường SwiftUI qua @Environment(\.managedObjectContext). Ngữ cảnh thường được truyền từ gốc ứng dụng, nơi NSPersistentContainer được tạo. Mô hình tiêu chuẩn là sử dụng ViewModel hoặc App struct để cấu hình ngăn xếp Core Data và truyền ngữ cảnh qua bộ sửa đổi .environment().
@FetchRequest chấp nhận một số tham số để cấu hình truy xuất. SortDescriptors — một mảng NSSortDescriptor xác định thứ tự bản ghi. Predicate — NSPredicate để lọc, ví dụ: chỉ truy xuất tác vụ có trạng thái “hoàn thành”. Animation — hoạt ảnh để cập nhật danh sách khi dữ liệu thay đổi.
Các vị từ Core Data sử dụng định dạng biểu thức giống SQL: NSPredicate(format: “status == %@”, “completed”). Có thể kết hợp nhiều điều kiện qua AND, OR và NOT. SortDescriptors xác định thứ tự: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). Để sắp xếp theo nhiều trường, một mảng các bộ mô tả được truyền — cái đầu tiên được áp dụng làm chính, những cái còn lại làm phụ.
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 ?? "")
}
}
}
Trong ví dụ, @FetchRequest truy xuất tất cả tác vụ chưa hoàn thành (isCompleted == NO), được sắp xếp trước theo ngày (tăng dần), sau đó theo mức độ ưu tiên (giảm dần). FetchedResults<Task> là một bộ sưu tập tự động cập nhật hoạt động như một mảng cho ForEach và List. Hoạt ảnh .default đảm bảo sự xuất hiện/biến mất mượt mà của các bản ghi khi có thay đổi.
@FetchRequest kết hợp hoàn hảo với List và ForEach để hiển thị danh sách Core Data động. Kết quả truy xuất (FetchedResults) tuân thủ giao thức RandomAccessCollection, do đó có thể được sử dụng trực tiếp trong List và ForEach. Khi thêm, xóa hoặc sửa bản ghi, danh sách tự động được vẽ lại với hoạt ảnh.
Để xóa bản ghi, phương thức ngữ cảnh delete được sử dụng. Để thêm bản ghi, một NSManagedObject mới được tạo và save được gọi. Tất cả thay đổi được thực hiện qua viewContext, cùng với @FetchRequest đảm bảo tính nhất quán dữ liệu. Để chỉnh sửa, @ObservedObject hoặc @Bindable được sử dụng cho một đối tượng Core Data cụ thể.
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()
}
}
Ví dụ này minh họa CRUD đầy đủ với @FetchRequest. Thêm: tạo Task qua ngữ cảnh và save(). Xóa: delete() trên ngữ cảnh và save(). Cập nhật: chuyển đổi thuộc tính và save(). Sau mỗi save(), @FetchRequest tự động truy xuất lại dữ liệu và List được cập nhật với hoạt ảnh. Các bộ sửa đổi onDelete và onTapGesture cung cấp tương tác iOS tiêu chuẩn.
Hãy xem xét một ví dụ ứng dụng nhật ký đầy đủ với @FetchRequest để truy xuất bản ghi. Biểu mẫu tìm kiếm sử dụng vị từ động: khi nhập văn bản, FetchRequest tự động cập nhật kết quả, lọc bản ghi theo tiêu đề và nội dung. Sắp xếp theo ngày tạo, từ mới nhất đến cũ nhất.
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)
}
}
Nhật ký có tìm kiếm: khi nhập văn bản vào trường searchable, một NSPredicate được tạo với CONTAINS[c] (tìm kiếm không phân biệt chữ hoa chữ thường) trên các trường title và content. Init chấp nhận searchText và tạo FetchRequest với vị từ tương ứng. Bộ sửa đổi searchable tự động cập nhật searchText, và @FetchRequest phản ứng với sự thay đổi vị từ và truy xuất lại dữ liệu.
Đối với các tình huống phức tạp hơn với nhiều bộ lọc, hãy kết hợp NSCompoundPredicate với logic AND/OR. @FetchRequest hỗ trợ các vị từ ở bất kỳ độ phức tạp nào, bao gồm các truy vấn con lồng nhau (SUBQUERY) để lọc theo relationship. Tuy nhiên, hãy nhớ: các vị từ phức tạp ảnh hưởng đến hiệu suất — đối với tập dữ liệu lớn (hơn 10.000 bản ghi), hãy sử dụng lập chỉ mục Core Data và giới hạn truy xuất qua fetchLimit.
@FetchRequest hỗ trợ vị từ động qua tham số predicate, có thể được thay đổi trong thời gian chạy. Để làm điều này, hãy tạo @FetchRequest với một vị từ biến và truyền nó qua Binding. Khi người dùng thay đổi bộ lọc — ví dụ: chọn một danh mục tác vụ — vị từ được cập nhật và việc truy xuất tự động khởi động lại.
Các vị từ động được triển khai qua thuộc tính @State hoặc @Published, được truyền dưới dạng Binding tới bộ khởi tạo @FetchRequest. SwiftUI theo dõi các thay đổi của Binding này và tạo lại NSFetchRequest với vị từ mới, tự động cập nhật danh sách.
Câu hỏi thường gặp
Thêm @Environment(\.managedObjectContext) var viewContext trong View gốc và truyền nó qua bộ sửa đổi .environment(\.managedObjectContext, context). Tất cả các View con sử dụng @FetchRequest sẽ tự động nhận ngữ cảnh từ môi trường.
Có, hãy sửa đổi ngữ cảnh Core Data: thêm, xóa hoặc cập nhật một đối tượng và gọi save(). @FetchRequest sẽ tự động thực hiện truy xuất lại và cập nhật giao diện. Để cập nhật cưỡng bức mà không cần save(), hãy sử dụng viewContext.refreshAllObjects().
@FetchRequest trả về một mảng phẳng các đối tượng. @SectionedFetchRequest nhóm kết quả theo một key path được chỉ định thành các phần, tương tự UITableView với sectionNameKeyPath. Sử dụng @SectionedFetchRequest cho danh sách được nhóm với tiêu đề phần.
Để lọc theo relationship, sử dụng ký hiệu dấu chấm: NSPredicate(format: “category.name == %@”, “Work”). @FetchRequest tự động thực hiện JOIN trong SQLite. Để truy cập relationship qua một đối tượng đã chọn, sử dụng các thuộc tính NSManagedObject thông thường.
@FetchRequest không hỗ trợ phân trang trực tiếp, vì nó được thiết kế để hiển thị tất cả bản ghi với cập nhật phản ứng. Để phân trang, hãy sử dụng NSFetchRequest với fetchLimit/fetchOffset và tải dữ liệu thủ công khi cuộn danh sách.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm