Sourcery لتحويل تلقائيا إلى هياكل كائن المجال

على الإنترنت ، وحتى على Habré ، هناك مجموعة من المقالات حول كيفية العمل مع Realm. قاعدة البيانات هذه مريحة للغاية وتتطلب الحد الأدنى من الجهد لكتابة التعليمات البرمجية ، إذا كنت تستطيع استخدامها. سوف تصف هذه المقالة طريقة العمل التي جئت إليها.

المشاكل


رمز التحسين


من الواضح ، في كل مرة تكتب رمز التهيئة لكائن Realm وتدعو نفس الوظائف لقراءة وكتابة الكائنات تكون غير مريحة. يمكنك لفه في التجريد.

مثال كائن الوصول إلى البيانات:

struct DAO<O: Object> { func persist(with object: O) { guard let realm = try? Realm() else { return } try? realm.write { realm.add(object, update: .all) } } func read(by key: String) -> O? { guard let realm = try? Realm() else { return [] } return realm.object(ofType: O.self, forPrimaryKey: key) } } 

استخدام:

 let yourObjectDAO = DAO<YourObject>() let object = YourObject(key) yourObjectDAO.persist(with: object) let allPersisted = yourObjectDAO.read(by: key) 

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

الوصول إليها من موضوع غير صحيح


عالم هو قاعدة بيانات آمنة الموضوع. الإزعاج الرئيسي الذي ينشأ من هذا هو عدم القدرة على نقل كائن من النوع Realm.Object من مؤشر ترابط إلى آخر.

كود:

 DispatchQueue.global(qos: .background).async { let objects = yourObjectDAO.read(by: key) DispatchQueue.main.sync { print(objects) } } 

سوف يعطي خطأ:

 Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.' 

بالطبع ، يمكنك العمل مع الكائن طوال الوقت في مؤشر ترابط واحد ، ولكن في الواقع هذا يخلق بعض الصعوبات التي من الأفضل التحايل عليها.

بالنسبة للحل ، "من المريح" تحويل Realm.Object إلى بنيات سيتم نقلها بهدوء بين خيوط مختلفة.

كائن المجال:

 final class BirdObj: Object { @objc dynamic var id: String = "" @objc dynamic var name: String = "" override static func primaryKey() -> String? { return "id" } } 

هيكل:

 struct Bird { var id: String var name: String } 

لتحويل الكائنات إلى بنيات ، سوف نستخدم تطبيقات البروتوكول
الترجمة:

 protocol Translator { func toObject(with any: Any) -> Object func toAny(with object: Object) -> Any } 

بالنسبة إلى Bird ، سيبدو كما يلي:

 final class BirdTranslator: Translator { func toObject(with any: Any) -> Object { let any = any as! Bird let object = BirdObj() object.id = any.id object.name = any.name return object } func toAny(with object: Object) -> Any { let object = object as! BirdObj return Bird(id: object.id, name: object.name) } } 

الآن يبقى تغيير DAO قليلاً بحيث يقبل وإرجاع الهياكل ، وليس كائنات المجال.

 struct DAO<O: Object> { private let translator: Translator init(translator: Translator) { self.translator = translator } func persist(with any: Any) { guard let realm = try? Realm() else { return } let object = translator.toObject(with: any) try? realm.write { realm.add(object, update: .all) } } func read(by key: String) -> Any? { guard let realm = try? Realm() else { return nil } if let object = realm.object(ofType: O.self, forPrimaryKey: key) { return translator.toAny(with: object) } else { return nil } } } 

يبدو أن المشكلة قد تم حلها. الآن سيعود DAO بنية Bird التي يمكن نقلها بحرية بين مؤشرات الترابط.

 let birdDAO = DAO<BirdObj>(translator: BirdTranslator()) DispatchQueue.global(qos: .background).async { let bird = birdDAO.read(by: key) DispatchQueue.main.sync { print(bird) } } 

كمية كبيرة من نفس النوع من الكود.


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

الكود الخاص بكل كيان هو نفسه في جوهره دائمًا. الفرق في ذلك يعتمد فقط على مجالات الهياكل.

يمكنك كتابة إنشاء تلقائي ، والذي سيحلل هياكلنا عن طريق إصدار Realm.Object و Translator لكل منهما. Sourcery يمكن أن تساعد في هذا. كان هناك بالفعل مقال عن هبرا حول الاستهزاء بمساعدتها.

من أجل إتقان هذه الأداة بمستوى كافٍ ، كان لدي أوصاف كافية لعلامات القوالب وعوامل تصفية الستينسل (على أساسها صنعت Sourcery) ووثائق Sourcery نفسها .

في مثالنا المحدد ، قد يبدو إنشاء Realm.Object كما يلي:

 import Foundation import RealmSwift #1 {% for type in types.structs %} #2 final class {{ type.name }}Obj: Object { #3 {% for variable in type.storedVariables %} {% if variable.typeName.name == "String" %} @objc dynamic var {{variable.name}}: String = "" {% endif %} {% endfor %} override static func primaryKey() -> String? { return "id" } } {% endfor %} 

# 1 - نحن نذهب من خلال جميع الهياكل.
# 2 - لكل منهما ، نقوم بإنشاء كائن فئة الورثة الخاصة بنا.
# 3 - لكل حقل مع اسم النوع = سلسلة ، إنشاء متغير بنفس الاسم والنوع. هنا يمكنك إضافة رمز للأولويات مثل Int ، Date ، وأكثرها تعقيدًا. أعتقد أن الجوهر واضح.

رمز إنشاء المترجم يشبهه.

 {% for type in types.structs %} final class {{ type.name }}Translator: Translator { func toObject(with entity: Any) -> Object { let entity = entity as! {{ type.name }} let object = {{ type.name }}Obj() {% for variable in type.storedVariables %} object.{{variable.name}} = entity.{{variable.name}} {% endfor %} return object } func toAny(with object: Object) -> Any { let object = object as! {{ type.name }}Obj return Bird( {% for variable in type.storedVariables %} {{variable.name}}: object.{{variable.name}}{%if not forloop.last%},{%endif%} {% endfor %} ) } } {% endfor %} 

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

بعد التثبيت ، يبقى لنا أن نكتب سطرًا واحدًا من شفرة bash لتشغيله في مشروع BuildPhase. يجب أن يتم إنشاؤه قبل بدء ترجمة ملفات مشروعك.



استنتاج


كان المثال الذي قدمته مبسّطًا جدًا. من الواضح أنه في المشروعات الكبيرة ، ستكون ملفات مثل .stencil أكبر من ذلك بكثير. في مشروعي ، يشغلون أقل بقليل من 200 سطر ، بينما يولدون 4000 سطر ويضيفون ، من بين أشياء أخرى ، إمكانية تعدد الأشكال في المجال.
بشكل عام ، لم أواجه بعض التأخير بسبب تحويل بعض الكائنات إلى أشياء أخرى.
سوف أكون سعيدًا بأي ملاحظات وانتقادات.

مراجع


عالم سريع
sourcery github
وثائق مصادر
الاستنسل المدمج في قالب العلامات والمرشحات
يسخر بسرعة مع Sourcery
إنشاء تطبيق تودو باستخدام عالم وسيفت

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


All Articles