@FetchRequest ใน SwiftUI — property wrapper สำหรับการดึงข้อมูลจาก Core Data โดยอัตโนมัติพร้อมการอัปเดต UI แบบรีแอกทีฟ Wrapper รับ NSFetchRequest พร้อมเพรดิเคตการกรองและการเรียงลำดับ และผลลัพธ์ — อาร์เรย์ของ NSManagedObject — จะอัปเดตโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงใด ๆ ในบริบทของ Core Data ตาม Apple Developer Documentation (2025), @FetchRequest ต้องใช้ managedObjectContext ในสภาพแวดล้อม SwiftUI และรองรับเพรดิเคตแบบไดนามิก ซึ่งให้การเชื่อมต่อแบบรีแอกทีฟระหว่าง Core Data และส่วนติดต่อผู้ใช้
ประเด็นสำคัญ
@FetchRequest คือ property wrapper ของ SwiftUI ที่สร้างและจัดการ FetchRequest จาก Core Data เมื่อเริ่มต้น Wrapper รับชนิดเอนทิตี อาร์เรย์ของ NSSortDescriptor สำหรับการเรียงลำดับ และ NSPredicate ที่เป็นตัวเลือกสำหรับการกรอง ผลลัพธ์จะถูกจัดเก็บเป็นอาร์เรย์ของวัตถุชนิด NSManagedObject ที่ระบุ และอัปเดตโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงในเลเยอร์การคงอยู่
คุณสมบัติหลักของ @FetchRequest คือการรีแอกทีฟ เมื่อข้อมูลใน Core Data เปลี่ยนแปลง (save, insert, delete, update) SwiftUI จะดำเนินการดึงข้อมูลอีกครั้งโดยอัตโนมัติและวาด Views ทั้งหมดที่ใช้ Fetch Request นี้ใหม่ นักพัฒนาไม่จำเป็นต้องเรียก refresh ด้วยตนเอง สมัครรับ NSManagedObjectContextDidSaveNotification หรือโหลดข้อมูลใหม่ SwiftUI จัดการวงจรการซิงโครไนซ์ทั้งหมด
เพื่อให้ @FetchRequest ทำงาน ต้องมี NSManagedObjectContext ในสภาพแวดล้อม SwiftUI ผ่าน @Environment(\.managedObjectContext) บริบทมักจะถูกส่งจากรากของแอปพลิเคชัน ซึ่งเป็นที่สร้าง NSPersistentContainer รูปแบบมาตรฐานคือการใช้ ViewModel หรือ App struct เพื่อกำหนดค่า Stack ของ 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 ผสานรวมกับ List และ ForEach ได้อย่างสมบูรณ์แบบสำหรับการแสดงรายการ Core Data แบบไดนามิก ผลลัพธ์การดึงข้อมูล (FetchedResults) สอดคล้องกับโปรโตคอล RandomAccessCollection ดังนั้นจึงสามารถใช้ได้โดยตรงใน List และ ForEach เมื่อเพิ่ม ลบ หรือแก้ไขเรกคอร์ด รายการจะถูกวาดใหม่โดยอัตโนมัติพร้อมอนิเมชัน
ในการลบเรกคอร์ด ใช้เมธอดบริบท delete ในการเพิ่มเรกคอร์ด สร้าง NSManagedObject ใหม่และเรียก save การเปลี่ยนแปลงทั้งหมดดำเนินการผ่าน viewContext ซึ่งร่วมกับ @FetchRequest รับประกันความสอดคล้องของข้อมูล สำหรับการแก้ไข ใช้ @ObservedObject หรือ @Bindable สำหรับวัตถุ Core Data เฉพาะ
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() การอัปเดต: สลับคุณสมบัติและ 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 จะสร้าง NSPredicate ด้วย CONTAINS[c] (การค้นหาแบบไม่คำนึงถึงตัวพิมพ์ใหญ่/เล็ก) ในฟิลด์ title และ content Init รับ searchText และสร้าง FetchRequest ด้วยเพรดิเคตที่เกี่ยวข้อง ตัวปรับเปลี่ยน searchable อัปเดต searchText โดยอัตโนมัติ และ @FetchRequest ตอบสนองต่อการเปลี่ยนแปลงของเพรดิเคตและดึงข้อมูลอีกครั้ง
สำหรับสถานการณ์ที่ซับซ้อนมากขึ้นด้วยตัวกรองหลายตัว ให้รวม NSCompoundPredicate ด้วยตรรกะ AND/OR @FetchRequest รองรับเพรดิเคตทุกระดับความซับซ้อน รวมถึงการค้นหาย่อยแบบซ้อน (SUBQUERY) สำหรับการกรองตามความสัมพันธ์ อย่างไรก็ตาม โปรดจำไว้ว่า: เพรดิเคตที่ซับซ้อนส่งผลต่อประสิทธิภาพ — สำหรับชุดข้อมูลขนาดใหญ่ (มากกว่า 10,000 เรกคอร์ด) ให้ใช้การสร้างดัชนีของ Core Data และจำกัดการดึงข้อมูลผ่าน fetchLimit
@FetchRequest รองรับ เพรดิเคตแบบไดนามิก ผ่านพารามิเตอร์ predicate ซึ่งสามารถเปลี่ยนแปลงได้ในรันไทม์ ในการทำเช่นนี้ ให้สร้าง @FetchRequest ด้วยเพรดิเคตตัวแปรและส่งผ่าน Binding เมื่อผู้ใช้เปลี่ยนตัวกรอง — ตัวอย่างเช่น เลือกหมวดหมู่งาน — เพรดิเคตจะอัปเดตและการดึงข้อมูลจะเริ่มต้นใหม่โดยอัตโนมัติ
เพรดิเคตแบบไดนามิกถูกนำไปใช้ผ่านคุณสมบัติ @State หรือ @Published ซึ่งถูกส่งเป็น Binding ไปยังตัวเริ่มต้นของ @FetchRequest SwiftUI ติดตามการเปลี่ยนแปลงของ Binding นี้และสร้าง NSFetchRequest ใหม่ด้วยเพรดิเคตใหม่ อัปเดตรายการโดยอัตโนมัติ
คำถามที่พบบ่อย
เพิ่ม @Environment(\.managedObjectContext) var viewContext ใน View หลักและส่งผ่านตัวปรับเปลี่ยน .environment(\.managedObjectContext, context) Views ลูกทั้งหมดที่ใช้ @FetchRequest จะรับบริบทจากสภาพแวดล้อมโดยอัตโนมัติ
ได้ แก้ไขบริบท Core Data: เพิ่ม ลบ หรืออัปเดตวัตถุและเรียก save() @FetchRequest จะดำเนินการดึงข้อมูลอีกครั้งโดยอัตโนมัติและอัปเดต UI สำหรับการบังคับอัปเดตโดยไม่ต้อง save() ให้ใช้ viewContext.refreshAllObjects()
@FetchRequest ส่งคืนอาร์เรย์แบบเรียบของวัตถุ @SectionedFetchRequest จัดกลุ่มผลลัพธ์ตามคีย์พาธที่ระบุเป็นส่วน ๆ คล้ายกับ UITableView ด้วย sectionNameKeyPath ใช้ @SectionedFetchRequest สำหรับรายการแบบกลุ่มที่มีหัวข้อส่วน
สำหรับการกรองตามความสัมพันธ์ ให้ใช้สัญกรณ์จุด: NSPredicate(format: “category.name == %@”, “Work”) @FetchRequest ดำเนินการ JOIN ใน SQLite โดยอัตโนมัติ ในการเข้าถึงความสัมพันธ์ผ่านวัตถุที่เลือก ให้ใช้คุณสมบัติ NSManagedObject ปกติ
@FetchRequest ไม่รองรับการแบ่งหน้าโดยตรง เนื่องจากถูกออกแบบมาสำหรับแสดงเรกคอร์ดทั้งหมดด้วยการอัปเดตแบบรีแอกทีฟ สำหรับการแบ่งหน้า ให้ใช้ NSFetchRequest ด้วย fetchLimit/fetchOffset และโหลดข้อมูลด้วยตนเองเมื่อเลื่อนรายการ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ