في الآونة الأخيرة ، بدأت العمل في مشروع كبير باستخدام Core Data. الشيء المعتاد هو أن الأشخاص في المشاريع يتغيرون ، يتم فقد الخبرة ، ويتم نسيان الفروق الدقيقة. من المستحيل تعميق الجميع في دراسة إطار محدد - كل شخص لديه مشاكله في العمل. لذلك ، قمت بإعداد عرض تقديمي قصير ، من تلك النقاط التي أعتبرها مهمة أو غير مغطاة بشكل كاف في البرامج التعليمية. أشارك الجميع وأتمنى أن يساعد ذلك في كتابة تعليمات برمجية فعالة وعدم ارتكاب أخطاء. من المفترض أنك بالفعل قليلا في هذا الموضوع.
سأبدأ مع عاديا.
البيانات الأساسية هي إطار عمل يدير ويخزن البيانات في تطبيق ما. يمكنك اعتبار Core Data بمثابة التفاف على وحدة تخزين علائقية فعلية تمثل البيانات في شكل كائنات ، في حين أن البيانات الأساسية نفسها ليست قاعدة بيانات.
كائنات البيانات الأساسية

لإنشاء
مساحة تخزين ، يستخدم التطبيق
فئات NSPersistentStoreCoordinator أو
NSPersistentContainer . ينشئ NSPersistentStoreCoordinator تخزينًا للنوع المحدد استنادًا إلى النموذج ، يمكنك تحديد الموقع وخيارات إضافية. يمكن استخدام NSPersistentContainer مع iOS10 ، ويوفر القدرة على إنشاء الحد الأدنى من الكود.
تعمل كما يلي: في حالة وجود قاعدة بيانات على المسار المحدد ، يتحقق المنسق من نسختها ، وإذا كان ضروريًا ، فيجري عملية ترحيل. إذا كانت قاعدة البيانات غير موجودة ، فسيتم إنشاؤها بناءً على نموذج NSManagedObjectModel. لكي يعمل كل هذا بشكل صحيح ، قبل إجراء تغييرات على النموذج ، قم بإنشاء إصدار جديد في Xcode من خلال محرر القائمة -> إضافة إصدار موديل. إذا حصلت على المسار ، يمكنك العثور على القاعدة وفتحها في المحاكي.
مثال مع NSPersistentStoreCoordinatorvar persistentCoordinator: NSPersistentStoreCoordinator = { let modelURL = Bundle.main.url(forResource: "Test", withExtension: "momd") let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL!) let persistentCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!) let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] let storeURL = URL(fileURLWithPath: documentsPath.appending("/Test.sqlite")) print("storeUrl = \(storeURL)") do { try persistentCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: [NSSQLitePragmasOption: ["journal_mode":"MEMORY"]]) return persistentCoordinator } catch { abort() } } ()
مثال NSPersistentContainer var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "CoreDataTest") container.loadPersistentStores(completionHandler: { (storeDescription, error) in print("storeDescription = \(storeDescription)") if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container } ()
تستخدم Core Data 4 أنواع من التخزين:
- سكليتي
- ثنائي
- في الذاكرة
- XML (في Mac OS فقط)
على سبيل المثال ، إذا كنت لا ترغب ، لأسباب أمنية ، في تخزين البيانات في نموذج ملف ، ولكن في نفس الوقت تريد استخدام التخزين المؤقت أثناء الجلسة والبيانات في شكل كائنات ، يكون تخزين نوع "In-Memory" مناسبًا تمامًا. في الواقع ، لا يُمنع وجود عدة أنواع مختلفة من المخازن في تطبيق واحد.
أريد أن أقول بضع كلمات عن كائن
NSManagedObjectContext . بشكل عام ، توفر Apple صيغة غامضة جدًا لـ NSManagedObjectContext - بيئة للعمل مع كائنات البيانات الأساسية. كل هذا من الرغبة في الانفصال عن الارتباطات مع قواعد البيانات العلائقية ، وتقديم البيانات الأساسية كأداة سهلة الاستخدام لا تتطلب فهم المفاتيح والمعاملات وسمات بازدان الأخرى. ولكن بلغة قواعد البيانات العلائقية ، يمكن تسمية NSManagedObjectContext ، بمعنى من المعاني ، مدير المعاملات. ربما لاحظت أنه يحتوي على طرق للحفظ والاستعادة ، على الرغم من أنك على الأرجح تستخدم الطريقة الأولى فقط.
يؤدي سوء فهم هذه الحقيقة البسيطة إلى استخدام نموذج السياق الواحد ، حتى في الحالات التي لا يكون فيها ذلك كافياً. على سبيل المثال ، تقوم بتحرير مستند كبير ، وفي نفس الوقت كان عليك تنزيل عدة أدلة. في أي نقطة تسمون حفظ؟ إذا كنا نعمل مع قاعدة بيانات علائقية ، فلن يكون هناك سؤال ، حيث سيتم تنفيذ كل عملية في معاملتها الخاصة. يحتوي Core Data أيضًا على طريقة ملائمة للغاية لحل هذه المشكلة - وهذا جزء من السياق الفرعي. ولكن لسوء الحظ ، لسبب ما نادرا ما يستخدم هذا. هنا يوجد
مقال جيد حول هذا الموضوع.
الميراث
لسبب ما لا أفهمه ، يوجد عدد كبير جدًا من الأدلة والأمثلة حيث لا يتم استخدام الميراث لـ Entity / NSManagedObject (الجداول) بأي طريقة. وفي الوقت نفسه ، إنها أداة مريحة للغاية. إذا لم تستخدم الوراثة ، فيمكنك تعيين قيم للسمات (الحقول) فقط من خلال آلية KVC ، والتي لا تتحقق من أسماء وأنواع السمات ، وهذا يمكن أن يؤدي بسهولة إلى أخطاء وقت التشغيل.
تتم إعادة تعريف الفئة لـ NSManagedObject في مصمم البيانات الأساسية:

الوراثة وتوليد الكود
بعد تحديد اسم الفصل لـ Entity ، يمكنك استخدام توليد الكود والحصول على فصل دراسي به كود جاهز:


إذا كنت ترغب في رؤية الشفرة التلقائية ، لكنك لا ترغب في إضافة ملفات إلى المشروع ، يمكنك استخدام طريقة أخرى: تعيين خيار "Codegen" للكيان. في هذه الحالة ، يجب البحث عن الرمز في ../ DerivedData / ...

استخدام توليد الشفرة لإنشاء فئات ، يمكن أن تؤدي الأخطاء المطبعية في أسماء المتغيرات إلى أخطاء وقت التشغيل.
إليك بعض الرموز مثل هذا:
@objc public class Company: NSManagedObject { @NSManaged public var inn: String? @NSManaged public var name: String? @NSManaged public var uid: String? @NSManaged public var employee: NSSet? }
بشكل سريع ،NSManaged له نفس معنى
الديناميكي في الهدف C.
تهتم Core Data نفسها بتلقي البيانات (لديها وحدات وصول داخلية) لسمات فئاتها. إذا كان لديك حقول عبور ، فأنت بحاجة إلى إضافة وظائف لحسابها.
لم يكن للفئات الموروثة من NSManagedObject (الجداول) مُنشئ "منتظم" قبل IOS10 ، على عكس الفئات الأخرى. لإنشاء كائن من النوع Company ، كان من الضروري كتابة بنية خرقاء إلى حد ما باستخدام NSEntityDescription. الآن هناك طريقة أكثر ملاءمة للتهيئة من خلال السياق (NSManagedObjectContext). الرمز أدناه. لاحظ ميزة الميراث عند تعيين السمات عبر آلية KVC:
مساحة الاسم لـ NSManagedObject
شيء آخر جدير بالذكر هو مساحة الاسم.

لن تواجه أي صعوبة إذا كنت تعمل على ObjectiveC أو Swift. عادة ، يتم ملء هذا الحقل بشكل صحيح بشكل افتراضي. لكن في المشروعات المختلطة ، قد يكون مفاجأة لك أنه بالنسبة للفصول الدراسية في ObjectiveC و سويفت تحتاج إلى وضع خيارات مختلفة. في Swift ، يجب ملء "الوحدة النمطية". في حالة عدم اكتمال هذا الحقل ، ستتم إضافة بادئة باسم المشروع إلى اسم الفئة ، مما يؤدي إلى حدوث خطأ في وقت التشغيل. في Objetive C ، اترك "الوحدة النمطية" فارغة ، وإلا فلن يتم العثور على NSManagedObject عند الوصول إليها من خلال اسم الفئة.
الروابط بين الكائنات
من حيث المبدأ ، يتم تغطية موضوع العلاقات بشكل جيد ، لكنني أريد التركيز على كيفية إضافة كيانات تابعة إلى الوالد. لذلك ، أولاً ، تذكير سريع بآلية إنشاء الروابط. النظر في مثال تقليدي ، الشركة هي الموظفين ، والاتصال واحد لكثير:
- إنشاء اتصال على كل جانب (الجدول)
- بعد ذلك ، يصبح الحقل معكوسًا ، ويجب ملؤه في كل جدول.


تصر Apple على تحديد العلاقات العكسية. في الوقت نفسه ، لا يعزز الانعكاس التماسك ، لكنه يساعد البيانات الأساسية على تعقب التغييرات على جانبي الاتصال ، من المهم تخزين المعلومات مؤقتًا وتحديثها.
من المهم أيضًا تحديد قاعدة الحذف بشكل صحيح. قاعدة الحذف هي إجراء سيتم تنفيذه باستخدام هذا الكائن عند حذف الكائن الأصل.
- تتالي - حذف جميع الكائنات التابعة ، عندما يتم حذف الوالد.
- رفض - حظر حذف أحد الوالدين إذا كان هناك طفل
- إلغاء - إلغاء المرجع الأصل
- لا يوجد إجراء - لم يتم تحديد أي إجراء ، فسيوفر تحذيرًا عند التحويل البرمجي
في هذا المثال ، عندما يتم حذف شركة ، سيتم حذف جميع الموظفين (تتالي). عند حذف موظف ، سيتم إعادة تعيين الرابط إليه في الشركة (الشاشة السابقة)
طرق لإضافة الكيانات التابعة إلى الوالد
1) الطريقة الأولى تضيف عبر NSSet. على سبيل المثال ، أضف موظفين إلى الشركة:
let set = NSMutableSet(); if let employee1 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee1.firstName = "" employee1.secondName = "" set.add(employee1) } if let emploee2 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee2.firstName = "" employee2.secondName = "" set.add(employee2) } company.employee = set
هذه الطريقة ملائمة لتهيئة الكائن أو ملء قاعدة البيانات. هناك فارق بسيط. إذا كان لدى الشركة موظفين بالفعل ، وقمت بتعيين مجموعة جديدة ، فسيقوم الموظفون السابقون بإعادة تعيين الرابط إلى الشركة ، لكن لن يتم حذفهم. بدلاً من ذلك ، يمكنك الحصول على قائمة الموظفين والعمل بالفعل مع هذه المجموعة.
let set = company.mutableSetValue(forKey: "employee")
2) إضافة كائنات تابعة عبر معرف الأصل
if let employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee.firstName = "" employee.secondName = "" employee.company = company }
الطريقة الثانية ملائمة عند إضافة أو تحرير كائن تابع في
شكل منفصل.
3) إضافة كائنات تابعة عبر طرق تم إنشاؤها تلقائيًا
extension Company { @objc(addEmployeeObject:) @NSManaged public func addEmployee(_ value: Employee) @objc(removeEmployeeObject:) @NSManaged public func removeFromEmployee(_ value: Employee) @objc(addEmployee:) @NSManaged public func addEmployee(_ values: NSSet) @objc(removeEmployee:) @NSManaged public func removeFromEmployee(_ values: NSSet) }
من أجل الاكتمال ، من المفيد معرفة هذه الطريقة ، لكن بطريقة ما لم تكن مفيدة بالنسبة لي ، وأنا أحذف الكود الإضافي حتى لا أفسد المشروع.
استعلامات جملة الطفل
في البيانات الأساسية ، لا يمكنك إجراء استعلام تعسفي بين أي بيانات ، كما يمكننا القيام به في SQL. لكن بين الكائنات التابعة ، من السهل التعقب باستخدام مسند قياسي. فيما يلي مثال على استعلام يحدد جميع الشركات التي يوجد فيها موظف بالاسم المحدد:
public static func getCompanyWithEmployee(name: String) -> [Company] { let request = NSFetchRequest<NSFetchRequestResult>(entityName: self.className()) request.predicate = NSPredicate(format: "ANY employee.firstName = %@", name) do { if let result = try moc.fetch(request) as? [Company] { return result } } catch { } return [Company]() }
ستبدو طريقة استدعاء الكود كما يلي:
لا تستخدم حقول النقل في الاستعلامات ؛ حيث لا يتم تحديد قيمها في وقت تنفيذ الاستعلام. لن يحدث أي خطأ ، لكن النتيجة ستكون غير صحيحة.
تحديد السمات (الحقول)
ربما لاحظت أن سمات الكيان لديها عدة خيارات.
مع اختياري ، كل شيء واضح من الاسم.
ظهر خيار استخدام نوع العدد في swif. الهدف - C لا يستخدم أنواع العددية للسمات ، لأنها لا يمكن أن تكون صفرية. ستؤدي محاولة تعيين قيمة عددية لسمة من خلال KVC إلى حدوث خطأ في وقت التشغيل. هذا يجعل من الواضح لماذا لا تحتوي أنواع السمات في البيانات الأساسية على مراسلات صارمة مع أنواع اللغات. في سويفت ، وفي المشاريع المختلطة ، يمكن استخدام سمات النوع القياسي.
سمات النقل هي حقول محسوبة
لا يتم تخزينها في قاعدة البيانات. يمكن استخدامها للتشفير. تتلقى هذه السمات القيم من خلال أداة الوصول المتجاوزة ، أو من خلال تعيين البدائل حسب الحاجة (على سبيل المثال ، will overridden willSave and awakeFromFetch).
Accessors سمة:
إذا لم تكن بحاجة إلى استخدام الحقول المحسوبة ، على سبيل المثال ، لإجراء التشفير أو أي شيء آخر ، فلا داعي للتفكير في ماهية ملحقات السمات. وفي الوقت نفسه ، فإن عمليات الحصول على القيم وتعيينها للسمات ليست "ذرية". لفهم ما أعنيه ، انظر الكود أدناه:
استخدم الأوليات في أحداث NSManagedObject بدلاً من المهمة المعتادة لتجنب تنفيذ الحلقات. مثال:
override func willSave() { let nameP = encrypt(field: primitiveValue(forKey: "name"), password: password) setPrimitiveValue(nameP, forKey: "nameC") super.willSave() } override func awakeFromFetch() { let nameP = decrypt(field: primitiveValue(forKey: "nameC"), password: password) setPrimitiveValue(nameP, forKey: "name") super.awakeFromFetch() }
إذا كان عليك فجأة في وقت ما أن تضغط على وظيفة awakeFromFetch في مشروع ، فسوف يفاجأ أنه يعمل بشكل غريب للغاية ، ولكن في الواقع لا يتم استدعاؤه على الإطلاق عند تنفيذ الطلب. هذا يرجع إلى حقيقة أن Core Data لديها آلية تخزين ذكية للغاية ، وإذا كان التحديد موجودًا بالفعل في الذاكرة (على سبيل المثال ، لأنك فقط ملأت هذا الجدول) ، فلن يتم استدعاء الطريقة. ومع ذلك ، أظهرت تجاربي أنه من حيث القيم المحسوبة ، يمكنك الاعتماد بأمان على استخدام awakeFromFetch ، كما تقول وثائق Apple. إذا كنت تحتاج للاختبار والتطوير لفرض الاستيقاظ من إحضار ، أضف مدار_إدارة_المحتوى.إصلاح_الحقول () قبل الطلب.
هذا كل شيء.
شكرا لكل من قرأ حتى النهاية.