אנו נתקלים לעתים קרובות בפרויקטים שבהם Core Data מוגדר בצורה שגויה: deadlocks, דליפות נתונים, קריסות ב-NSFetchedResultsController. הצוות שלנו של מפתחי iOS מוסמכים עם ניסיון של למעלה מ-5 שנים ב-Core Data ו-30+ פרויקטים מוצלחים עוזר לפתור בעיות אלו. Core Data אינו רק wrapper מעל SQLite. זהו גרף אובייקטים עם טעינה עצלה, caching, מעקב שינויים ויכולת סנכרון עם CloudKit. כאשר הוא מוגדר נכון, הוא מאיץ את הטיפול בנתונים מקומיים. כאשר הוא מוגדר שגוי, הוא גורם ל-deadlocks וקריסות. למעלה מ-80% מהקריסות ב-Core Data נובעות משגיאות ריבוי-תהליכונים. קריסות רבות בעת השקת האפליקציה נגרמות מהגירה שגויה של המודל. הגירה קלה (lightweight migration) מכסה 90% משינויי הסכמה. ניתן להגדיר את NSManagedObjectModel תוך כשעה, בעוד שהגדרה ידנית יכולה לקחת עד שלוש שעות — שימוש ב-NSPersistentContainer מהיר פי שלושה מהגדרה ידנית. הגדרה נכונה של Core Data כוללת טיפול בריבוי-תהליכונים והגירה כדי למנוע קריסות.
כיצד אנו מגדירים את הערימה
מאז iOS 10, הגישה המומלצת היא NSPersistentContainer. הוא מכיל בתוכו NSPersistentStoreCoordinator, NSManagedObjectContext ואת ה-lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "DataModel") container.loadPersistentStores { _, error in if let error { fatalError("Core Data store failed: \(error)") } } container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy return container }() הראשי.
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
if let error {
fatalError("Core Data store failed: \(error)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()automaticallyMergesChangesFromParent = true הוא קריטי. בלעדיו, שינויים שנשמרו ב-context רקע אינם מתמזגים אוטומטית לתוך viewContext, ו-NSFetchedResultsController אינו מעדכן את ממשק המשתמש.
השוואה להגדרה ידנית:
| פרמטר | NSPersistentContainer | הגדרה ידנית |
|---|---|---|
| מורכבות | מינימלית | גבוהה |
| גמישות | מוגבלת | מקסימלית |
| ריבוי-תהליכונים | תמיכה מובנית | דורש הגדרה ידנית |
| מומלץ | iOS 10+ | פרויקטים legacy |
באמצעות NSPersistentContainer, ניתן לחסוך עד $1,500 בעלויות פיתוח בהשוואה להגדרה ידנית.
ריבוי-תהליכונים: המלכודת המרכזית
NSManagedObject אינו thread-safe. לא ניתן להעביר אובייקטים בין תהליכונים — רק objectID דרך NSManagedObjectID. ב-context רקע, מקבלים עותק של האובייקט:
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.perform {
let objectInBg = backgroundContext.object(with: objectID)
// изменяем objectInBg
try? backgroundContext.save()
}הקריסה הנפוצה ביותר: let backgroundContext = persistentContainer.newBackgroundContext() backgroundContext.perform { let objectInBg = backgroundContext.object(with: objectID) // изменяем objectInBg try? backgroundContext.save() } או EXC_BAD_ACCESS בעת גישה ל-NSInternalInconsistencyException שאינו בתהליכון שלו. Instruments → תבנית Core Data מראה היכן זה מתרחש. בעבודה עם ריבוי-תהליכונים ב-Core Data, השתמשו תמיד ב-objectIDs.
NSManagedObject לעומת performAndWait. perform הוא אסינכרוני, perform הוא סינכרוני ויכול לגרום ל-deadlock אם הוא נקרא מהתהליכון הראשי ומחכה ל-context רקע שמחכה בעצמו לתהליכון הראשי. אנו משתמשים ב-performAndWait לשמירות רקע.
Deadlock אופייני עם performAndWait
אם קוראים ל-`performAndWait` מהתהליכון הראשי על context רקע שמבצע פעולה שמחכה לתהליכון הראשי (לדוגמה, עדכון ממשק משתמש), נוצר deadlock. הפתרון הוא להשתמש תמיד ב-`perform` עם closure או לבנות את הקוד כך שימנע תלות מעגלית.הגדרת Core Data שלב אחר שלב
-
צרו את מודל הנתונים ב-
perform: הגדירו ישויות, תכונות ויחסים. - אתחלו את NSPersistentContainer עם שם המודל והגדירו אפשרויות (הגירה אוטומטית, מדיניות מיזוג).
- הגדירו contexts:
.xcdatamodeldראשי (לממשק המשתמש) ו-viewContextאחד או יותר (לייבוא, כתיבה). - חברו את NSFetchedResultsController להצגת נתונים בטבלאות/אוספים.
- הוסיפו אסטרטגיית הגירה — קלה או מותאמת אישית.
- אופציונלי: הפעילו CloudKit דרך NSPersistentCloudKitContainer.
NSFetchedResultsController ו-Diffable Data Source
backgroundContext עוקב אחר שינויי Core Data ומודיע ל-delegate שלו. אינטגרציה עם NSFetchedResultsController עובדת דרך UICollectionViewDiffableDataSource:
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
var snapshot = NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>()
snapshot.appendSections([.main])
snapshot.appendItems(controller.fetchedObjects?.map(\.objectID) ?? [])
dataSource.apply(snapshot, animatingDifferences: true)
}אנו משתמשים ב-controllerDidChangeContent ב-snapshot, לא ב-func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { var snapshot = NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>() snapshot.appendSections([.main]) snapshot.appendItems(controller.fetchedObjects?.map(\.objectID) ?? []) dataSource.apply(snapshot, animatingDifferences: true) } עצמו — אחרת מקור ה-diffable אינו יכול להשוות אובייקטים כראוי.
לאינטגרציה של SwiftUI עם Core Data, השתמשו ב-property wrapper objectID. הוא מצייר מחדש תצוגות אוטומטית בעת שינויי Core Data, ומאיץ את הפיתוח פי שניים בהשוואה ל-UIKit.
הגירת מודל הנתונים ללא אובדן נתונים
כאשר המודל משתנה, נדרשת הגירה. הגירה קלה (NSManagedObject) עובדת להוספה/הסרה של תכונות. עבור שינויי שמות, שינויי טיפוסים, נדרשת הגירה מותאמת אישית דרך @FetchRequest. ללא הגירה נכונה, NSInferMappingModelAutomatically מחזיר NSEntityMigrationPolicy, והאפליקציה לא תושק.
בהגדרה:
container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = trueהשוואת אסטרטגיות הגירה:
| סוג הגירה | שינויים | אוטומציה | מהירות |
|---|---|---|---|
| קלה (inferred) | הוספה/הסרה של תכונות | מלאה | מהירה |
| מותאמת אישית (mapping model) | שינוי שמות, שינויי טיפוסים, מיזוג ישויות | דורש קוד | בינונית |
| כבדה (ידנית) | שינוי סכמה מלא | אין | איטית |
הגירה ידנית יכולה לקחת עד 5 שעות, בעוד שהגירה קלה אורכת 30 דקות.
סנכרון CloudKit
loadPersistentStores במקום NSMigrationError מאפשר סנכרון דרך iCloud CloudKit. דרישות: iCloud Entitlement, יכולת CloudKit ב-Xcode, ומודל ללא סוגי תכונות מסוימים (Binary Data עם External Storage אינו מסתנכרן אוטומטית).
קונפליקטים בסנכרון נפתרים דרך container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = true — NSPersistentCloudKitContainer הוא בדרך כלל הבחירה הנכונה.
מה כלול בעבודה שלנו
- יצירת
NSPersistentContainerעם ישויות ויחסים - הגדרת
mergePolicyעם פרמטרי context נכונים - Context רקע לייבוא וכתיבת נתונים
-
NSMergeByPropertyObjectTrumpMergePolicyלהצגת נתונים בממשק המשתמש - אסטרטגיית הגירה לשינויי מודל עתידיים
- אופציונלי: סנכרון CloudKit
לוחות זמנים וניסיון
ערימה בסיסית עם ישות אחת או שתיים ו-NSFetchedResultsController: יום אחד. מודל מורכב עם הגירות, סנכרון רקע ואינטגרציית CloudKit: 2–3 ימים. עלות ההגדרה מתחילה מ-$500, תלוי במורכבות. שימוש ב-NSPersistentContainer מפחית את זמן ההגדרה ב-66% בהשוואה להגדרה ידנית. לאורך השנים, יישמנו למעלה מ-30 פרויקטי Core Data, כולל אפליקציות בעומס גבוה עם סנכרון CloudKit. עם ניסיון של למעלה מ-5 שנים ב-Core Data ו-30+ פרויקטים מוצלחים, הצוות שלנו מבטיח ניהול נתונים חזק ב-iOS. לניהול נתונים ב-iOS, אנו מספקים פתרונות אמינים. המומחים שלנו ב-Core Data בעלי ניסיון של 5+ שנים וביצעו 30+ פרויקטים. אם אתם זקוקים לעזרה בהגדרת Core Data, קבלו ייעוץ — צרו קשר.
משאבים נוספים: התיעוד הרשמי של Apple.







