سويفت: حاويات لتخزين القيم الأساسية


تخيل أنك بحاجة إلى حفظ معرف المستخدم في UserDefaults . ماذا ستكون الخطوة الأولى؟


عادةً ما تبدأ الأعمال بإضافة ثابت للمفتاح والتحقق من تفرده. هذا صحيح بالنسبة لمعظم المتاجر الأخرى ذات القيمة الرئيسية. ولا تقتصر عواقب التصميم البدائي لهذه المستودعات على المفاتيح ؛ فالواجهة في شكل مجموعة من الطرق غير المنتظمة تؤدي إلى عدد من المشاكل المحتملة:


  • أخطاء في كتابة المفاتيح : يمكن استخدام مفاتيح مختلفة لقراءة وكتابة نفس الكيان.
  • نوع القيمة غير المثبت : على سبيل المثال ، يمكنك من خلال نفس المفتاح كتابة رقم وقراءة سلسلة.
  • تصادم المفتاح : يمكن تسجيل كيانات مختلفة لها نفس المفتاح في أجزاء مختلفة من المشروع.

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


بروتوكول التخزين


لذا ، فإن أول ما نحتاج إليه هو بروتوكول للمستودع نفسه ، والذي سيساعد على التجريد من نوعه. لذلك ، سيكون لدينا واجهة واحدة للعمل مع UserDefaults ، ومع سلسلة مفاتيح ، ومع بعض التخزين ذي القيمة الأساسية. يبدو هذا البروتوكول بسيطًا جدًا:


 protocol KeyValueStorage { func value<T: Codable>(forKey key: String) -> T? func setValue<T: Codable>(_ value: T?, forKey key: String) } 

لذلك أي تخزين يتوافق مع بروتوكول KeyValueStorage يجب أن يطبق طريقتين عامتين: getter و setter من قيم المفاتيح في شكل سلسلة. في الوقت نفسه ، تتوافق القيم نفسها مع بروتوكول Codable ، الذي يسمح بتخزين مثيلات الأنواع التي لها تمثيل عالمي (على سبيل المثال ، JSON أو PropertyList ).


لا تدعم تطبيقات مستودع البيانات القياسية نوع Codable للقيم ، على سبيل المثال ، UserDefaults نفسه. لذلك ، فإن هذا النوع من التمييز هو مكافأة جانبية تتيح لك تخزين كل من Swift البدائية (الأرقام والسلاسل ، إلخ) وبنى البيانات بالكامل ، مع الحفاظ على واجهة التخزين بسيطة.


تنفيذ البروتوكول


هناك طريقتان لتطبيق بروتوكول KeyValueStorage :


  • قم بالتوقيع على وحدة التخزين الحالية بموجب البروتوكول وأضف الطرق التالية:

 extension UserDefaults: KeyValueStorage { func value<T: Codable>(forKey key: String) -> T? { //   } func setValue<T: Codable>(_ value: T?, forKey key: String) { //   } } 

  • لف التخزين في نوع منفصل ، مع إخفاء مجالاته للاستخدام الخارجي:

 class PersistentStorage: KeyValueStorage { private let userDefaults: UserDefaults let suiteName: String? let keyPrefix: String init?(suiteName: String? = nil, keyPrefix: String = "") { guard let userDefaults = UserDefaults(suiteName: suiteName) else { return nil } self.userDefaults = userDefaults self.suiteName = suiteName self.keyPrefix = keyPrefix } func value<T: Codable>(forKey key: String) -> T? { //   } func setValue<T: Codable>(_ value: T?, forKey key: String) { //   } } 

تبدو الطريقة الثانية أكثر تعقيدًا ، ولكن بشكل أفضل ، على سبيل المثال ، يمكنك تعيين بادئة المفتاح لكل مثيل تخزين. بالإضافة إلى ذلك ، يخفي المجمع مجالات إضافية ويترك فقط حقول البروتوكول المتاحة ، وبالتالي يحل المشاكل المحتملة لتعارض التوقيع.


لتطبيق value(forKey:) الأساليب value(forKey:) و setValue(:forKey:) من المهم توفير توافق البيانات. يعد ذلك ضروريًا حتى يمكن استرداد القيم المخزنة بواسطة أدوات UserDefaults القياسية بطرق من KeyValueStorage ، والعكس.


يتوفر هنا مثال كامل لفئة PersistentStorage الجاهزة للاستخدام.


حاوية القيمة


الآن وبعد أن استخلصنا من نوع التخزين ، سنضيف حاوية للقيمة حسب المفتاح. سيكون مفيدًا لتضمين جميع الحقول اللازمة في كيان واحد مناسب ، والذي يمكن نقله واستخدامه بشكل منفصل عن التخزين نفسه. يتم تنفيذ هذه الحاوية كفئة عامة صغيرة:


 class KeyValueContainer<T: Codable> { let storage: KeyValueStorage let key: String var value: T? { get { storage.value(forKey: key) } set { storage.setValue(newValue, forKey: key) } } init(storage: KeyValueStorage, key: String) { self.storage = storage self.key = key } } 

يقتصر بروتوكول Codable على نوع قيمة الحاوية بنفس الطريقة المتبعة في أساليب المتجر نفسه ، وبالتالي فإن value الخاصية المحسوبة تقوم بالاتصال مع المكالمات باستخدام مفتاح ثابت ونوع قيمة له.


إذا كنت ترغب في ذلك ، يمكنك توسيع وظيفة الحاوية ، على سبيل المثال ، قم بإضافة قيمة افتراضية سيتم استخدامها في حالة عدم وجود بيانات للمفتاح في المتجر.


مثال على تنفيذ الحاوية ذات القيمة الافتراضية
 class KeyValueContainer<T: Codable> { let storage: KeyValueStorage let key: String let defaultValue: T? var value: T? { get { storage.value(forKey: key) ?? defaultValue } set { storage.setValue(newValue, forKey: key) } } init(storage: KeyValueStorage, key: String, defaultValue: T? = nil) { self.storage = storage self.key = key self.defaultValue = defaultValue } } 

تعمل الحاوية على حل مشكلتينا الأولتين - حيث تقوم بإصلاح المفتاح ونوع البيانات التي تتم قراءتها وكتابتها. لذلك سيتم إيقاف أي محاولة لكتابة قيمة من نوع غير صحيح بواسطة برنامج التحويل البرمجي في مرحلة الإنشاء:


 func doSomething(with container: KeyValueContainer<Int>) { container.value = "Text" //   } 

يبقى لحل المشكلة الأخيرة - لضمان تفرد مفاتيح التخزين. هذا القلق يمكن أيضا أن يكون "معلق" على المترجم باستخدام خدعة مثيرة للاهتمام إلى حد ما.


التفرد الرئيسي


لحل مشكلة التعارض ، نستخدم اسم الخاصية المحسوبة التي سيتم فيها إنشاء الحاوية نفسها كمفتاح. للقيام بذلك ، أضف امتدادًا بسيطًا إلى بروتوكول KeyValueStorage بنا:


 extension KeyValueStorage { func makeContainer<T: Codable>(key: String = #function) -> KeyValueContainer<T> { KeyValueContainer(storage: self, key: key) } } 

لذلك في جميع تطبيقات بروتوكول التخزين ، ستتم إضافة طريقة عامة تقوم بإرجاع حاوية مع المفتاح المحدد. تحظى المعلمة key بأهمية خاصة في هذه الطريقة ، والتي تحتوي افتراضيًا على قيمة مساوية للتعبير الخاص #function ( التوثيق ). هذا يعني أنه في مرحلة #function استبدال #function الحرفي #function باسم الإعلان الذي تم استدعاء طريقة makeContainer(key:) .


يسمح لك هذا البناء بالإعلان عن الحاويات في امتدادات التخزين ، وستكون أسمائهم أسماء الخصائص المحسوبة إذا تم makeContainer() طريقة makeContainer() بدون المعلمة key فيها:


 extension PersistentStorage { var foobar: KeyValueContainer<Int> { makeContainer() } } 

في المثال ، ستتلقى مثيلات التخزين PersistentStorage خاصية foobar للحاوية باستخدام مفتاح foobar يحمل نفس الاسم ، والذي سيكون نوع قيمته عددًا صحيحًا. ستؤدي محاولة إضافة حاوية foobar ثانية للتخزين إلى حدوث خطأ في الترجمة ، مما يضمن لنا تفرد المفاتيح.



لتلخيص


حاويات القيمة تحل جميع مشكلات واجهة التخزين المذكورة ولا تقتصر على UserDefaults . يكفي توقيع (التفاف) أي تخزين قيمة المفتاح بموجب بروتوكول KeyValueStorage مع التنفيذ المقابل ، ويمكن استخدامه بالفعل لإنشاء حاويات آمنة.


بشكل منفصل ، تجدر الإشارة إلى راحة إضافة واستخدام هذه الحاويات ، كل شيء يقتصر على تصاميم بسيطة ومفهومة:


 extension PersistentStorage { //   var foobar: KeyValueContainer<Int> { makeContainer() } } //   let foobar = storageInstance.foobar.value //   storageInstance.foobar.value = 123 

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


هذا كل شيء. سأكون سعيدا لردود الفعل في التعليقات. وداعا!

Source: https://habr.com/ru/post/ar481456/


All Articles