Di
Internet , dan bahkan di
Habré , ada banyak artikel tentang cara bekerja dengan Realm. Basis data ini cukup nyaman dan membutuhkan sedikit usaha untuk menulis kode, jika Anda dapat menggunakannya. Artikel ini akan menjelaskan metode kerja yang saya gunakan.
Masalahnya
Optimasi kode
Jelas, setiap kali menulis kode inisialisasi untuk objek Realm dan memanggil fungsi yang sama untuk membaca dan menulis objek tidak nyaman. Anda bisa membungkusnya dengan abstraksi.
Contoh Objek Akses Data:
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) } }
Penggunaan:
let yourObjectDAO = DAO<YourObject>() let object = YourObject(key) yourObjectDAO.persist(with: object) let allPersisted = yourObjectDAO.read(by: key)
Anda dapat menambahkan banyak metode yang berguna ke DAO, misalnya: untuk menghapus, membaca semua objek dari jenis yang sama, mengurutkan, dan sejenisnya. Semuanya akan bekerja dengan salah satu objek Realm.
Diakses dari utas yang salah
Realm adalah basis data aman thread. Ketidaknyamanan utama yang muncul dari ini adalah ketidakmampuan untuk mentransfer objek bertipe Realm.Object dari satu thread ke yang lain.
Kode:
DispatchQueue.global(qos: .background).async { let objects = yourObjectDAO.read(by: key) DispatchQueue.main.sync { print(objects) } }
Itu akan memberikan kesalahan:
Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'
Tentu saja, Anda dapat bekerja dengan objek sepanjang waktu dalam satu utas, tetapi pada kenyataannya ini menciptakan kesulitan tertentu yang lebih baik untuk dielakkan.
Untuk solusinya, "nyaman" untuk mengubah Realm.Object menjadi struktur yang akan ditransfer secara diam-diam di antara berbagai utas.
Objek Realm:
final class BirdObj: Object { @objc dynamic var id: String = "" @objc dynamic var name: String = "" override static func primaryKey() -> String? { return "id" } }
Struktur:
struct Bird { var id: String var name: String }
Untuk mengonversi objek menjadi struktur, kita akan menggunakan implementasi protokol
Penerjemah:
protocol Translator { func toObject(with any: Any) -> Object func toAny(with object: Object) -> Any }
Untuk Bird, akan terlihat seperti ini:
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) } }
Sekarang tinggal sedikit mengubah DAO sehingga ia menerima dan mengembalikan struktur, bukan objek Realm.
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 } } }
Masalahnya tampaknya diselesaikan. Sekarang DAO akan mengembalikan struktur Burung yang dapat dengan bebas dipindahkan di antara utas.
let birdDAO = DAO<BirdObj>(translator: BirdTranslator()) DispatchQueue.global(qos: .background).async { let bird = birdDAO.read(by: key) DispatchQueue.main.sync { print(bird) } }
Sejumlah besar jenis kode yang sama.
Setelah memecahkan masalah mentransfer objek di antara utas, kami bertemu dengan yang baru. Bahkan dalam kasus kami yang paling sederhana, dengan kelas dua bidang, kami perlu menulis 18 baris kode tambahan. Bayangkan jika tidak ada 2 bidang, misalnya 10, dan beberapa di antaranya bukan tipe primitif, tetapi entitas yang juga perlu diubah. Semua ini menciptakan banyak baris dengan jenis kode yang sama. Perubahan sepele dalam struktur data dalam database memaksa Anda untuk naik ke tiga tempat.
Kode untuk setiap entitas selalu, pada intinya, sama. Perbedaannya hanya tergantung pada bidang struktur.
Anda dapat menulis pembuatan otomatis, yang akan menguraikan struktur kami dengan menerbitkan Realm.Object dan Penerjemah untuk masing-masingnya.
Sourcery dapat membantu dengan ini. Sudah ada artikel tentang
habra tentang
Mocking dengan bantuannya.
Untuk menguasai alat ini pada tingkat yang cukup, saya memiliki cukup deskripsi
tag templat dan filter Stensil (atas dasar pembuatan Sourcery) dan
dokumentasi Sourcery itu sendiri .
Dalam contoh khusus kami, generasi Realm.Object mungkin terlihat seperti ini:
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 - Kami melewati semua struktur.
# 2 - Untuk masing-masing, kita membuat Objek kelas pewaris kita sendiri.
# 3 - Untuk setiap bidang dengan tipe nama == String, buat variabel dengan nama dan tipe yang sama. Di sini Anda dapat menambahkan kode untuk primitif seperti Int, Date, dan yang lebih kompleks. Saya pikir intinya jelas.
Kode untuk menghasilkan Penerjemah tampak serupa.
{% 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 %}
Yang terbaik adalah menginstal Sourcery melalui manajer dependensi, menunjukkan versi sehingga apa yang Anda tulis bekerja untuk semua orang dengan cara yang sama dan tidak rusak.
Setelah instalasi, tetap bagi kami untuk menulis satu baris kode bash untuk menjalankannya di proyek BuildPhase. Itu harus dihasilkan sebelum file proyek Anda mulai dikompilasi.

Kesimpulan
Contoh yang saya berikan cukup disederhanakan. Jelas bahwa dalam proyek besar, file seperti .stencil akan jauh lebih besar. Dalam proyek saya, mereka menempati kurang dari 200 baris, sementara menghasilkan 4000 dan menambahkan, di antaranya, kemungkinan polimorfisme di Alam.
Secara umum, saya tidak mengalami penundaan karena konversi beberapa objek ke yang lain.
Saya akan dengan senang hati menerima umpan balik dan kritik.
Referensi
Kerajaan cepatSourcery githubDokumentasi sumberTag dan filter templat bawaan stensilMengejek dengan Sourcery dengan cepatMembuat Aplikasi ToDo Menggunakan Realm dan Swift