Data inti secara detail

Baru-baru ini, saya mulai mengerjakan proyek besar menggunakan Core Data. Yang biasa terjadi adalah orang-orang di proyek berubah, pengalaman hilang, dan nuansa dilupakan. Tidak mungkin untuk memperdalam semua orang ke dalam studi kerangka kerja tertentu - setiap orang memiliki masalah kerja mereka sendiri. Oleh karena itu, saya menyiapkan presentasi singkat, dari poin-poin yang saya anggap penting atau tidak cukup dibahas dalam tutorial. Saya berbagi dengan semua orang dan berharap ini akan membantu menulis kode yang efektif dan tidak membuat kesalahan. Diasumsikan bahwa Anda sudah sedikit dalam subjek.

Saya akan mulai dengan dangkal.

Data Inti adalah kerangka kerja yang mengelola dan menyimpan data dalam suatu aplikasi. Anda dapat mempertimbangkan Core Data sebagai pembungkus penyimpanan relasional fisik yang mewakili data dalam bentuk objek, sedangkan Core Data itu sendiri bukan database.

Objek Data Inti


gambar

Untuk membuat penyimpanan, aplikasi menggunakan kelas NSPersistentStoreCoordinator atau NSPersistentContainer . NSPersistentStoreCoordinator menciptakan penyimpanan dari tipe yang ditentukan berdasarkan model, Anda dapat menentukan lokasi dan opsi tambahan. NSPersistentContainer dapat digunakan dengan iOS10, memberikan kemampuan untuk membuat dengan jumlah kode minimum.

Ini berfungsi sebagai berikut: jika database ada di jalur yang ditentukan, koordinator memeriksa versinya dan, jika perlu, melakukan migrasi. Jika database tidak ada, maka itu dibuat berdasarkan pada model NSManagedObjectModel. Agar semua ini berfungsi dengan benar, sebelum membuat perubahan pada model, buat versi baru dalam Xcode melalui menu Editor -> Add Model Version. Jika Anda mendapatkan jalurnya, Anda dapat menemukan dan membuka basis di emulator.

Contoh dengan NSPersistentStoreCoordinator
var 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() } } () 

Contoh 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 } () 


Data Inti menggunakan 4 jenis penyimpanan:

- SQLite
- biner
- dalam memori
- XML โ€‹โ€‹(hanya Mac OS)

Jika, misalnya, untuk alasan keamanan, Anda tidak ingin menyimpan data dalam bentuk file, tetapi pada saat yang sama ingin menggunakan caching selama sesi dan data dalam bentuk objek, penyimpanan tipe "Dalam Memori" cukup cocok. Sebenarnya, tidak dilarang memiliki beberapa jenis penyimpanan dalam satu aplikasi.

Saya ingin mengatakan beberapa kata tentang objek NSManagedObjectContext . Secara umum, Apple memberikan kata-kata yang sangat kabur untuk NSManagedObjectContext - sebuah lingkungan untuk bekerja dengan objek Data Inti. Semua ini karena keinginan untuk memisahkan diri dari asosiasi dengan basis data relasional, dan untuk menyajikan Data Inti sebagai alat yang mudah digunakan yang tidak memerlukan pemahaman tentang kunci, transaksi, dan atribut Bazdan lainnya. Tetapi dalam bahasa basis data relasional, NSManagedObjectContext dapat, dalam arti tertentu, disebut manajer transaksi. Anda mungkin memperhatikan bahwa ia memiliki metode simpan dan kembalikan, meskipun kemungkinan besar Anda hanya menggunakan yang pertama.

Kesalahpahaman fakta sederhana ini mengarah pada penggunaan model konteks tunggal, bahkan dalam situasi di mana itu tidak cukup. Misalnya, Anda mengedit dokumen besar, dan pada saat yang sama Anda harus mengunduh beberapa direktori. Pada titik apa Anda menelepon save? Jika kami bekerja dengan database relasional, maka tidak akan ada pertanyaan, karena setiap operasi akan dilakukan dalam transaksi sendiri. Core Data juga memiliki cara yang sangat mudah untuk menyelesaikan masalah ini - ini adalah cabang dari konteks anak. Namun sayangnya, untuk beberapa alasan ini jarang digunakan. Di sini ada artikel bagus tentang topik ini.

Warisan


Untuk beberapa alasan saya tidak mengerti, ada sejumlah besar manual dan contoh di mana warisan untuk Entity / NSManagedObject (tabel) tidak digunakan dengan cara apa pun. Sementara itu, ini adalah alat yang sangat nyaman. Jika Anda tidak menggunakan warisan, maka Anda dapat menetapkan nilai ke atribut (bidang) hanya melalui mekanisme KVC, yang tidak memeriksa nama dan tipe atribut, dan ini dapat dengan mudah menyebabkan kesalahan runtime.

Redefinisi kelas untuk NSManagedObject dilakukan dalam desainer Data Inti:

gambar

Warisan dan pembuatan kode


Setelah menentukan nama kelas untuk Entity, Anda bisa menggunakan pembuatan kode dan mendapatkan kelas dengan kode siap pakai:

gambar

gambar

Jika Anda ingin melihat kode yang dibuat secara otomatis, tetapi tidak ingin menambahkan file ke proyek, Anda dapat menggunakan cara lain: setel opsi "Codegen" untuk Entity. Dalam hal ini, kode harus dicari di ../ DerivedData / ...

gambar

Gunakan pembuatan kode untuk membuat kelas, kesalahan ketik pada nama variabel dapat menyebabkan kesalahan runtime.

Berikut ini beberapa kode seperti ini:

 @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? } 

Secara cepat, @NSManaged memiliki arti yang sama dengan dinamis dalam Objective C.
Core Data sendiri menangani penerimaan data (memiliki pengakses internal) untuk atribut kelasnya. Jika Anda memiliki bidang transit, maka Anda perlu menambahkan fungsi untuk menghitungnya.

Kelas yang diwarisi dari NSManagedObject (tabel) tidak memiliki konstruktor "reguler" sebelum IOS10, tidak seperti kelas lainnya. Untuk membuat objek dengan tipe Company, perlu untuk menulis konstruksi yang agak canggung menggunakan NSEntityDescription. Sekarang ada metode inisialisasi yang lebih nyaman melalui konteks (NSManagedObjectContext). Kode di bawah. Perhatikan keuntungan pewarisan saat menetapkan atribut di atas mekanisme KVC:

 // 1 -    NSEntityDescription,    KVO let company1 = NSEntityDescription.insertNewObject(forEntityName: "Company", into: moc) company1.setValue("077456789111", forKey: "inn") company1.setValue(" ", forKey: "name") // 2 -    NSEntityDescription,     let company2 = NSEntityDescription.insertNewObject(forEntityName: "Company", into: moc) as! Company company2.inn = "077456789222" company2.name = " " // 3 -     (IOS10+),     let company3 = Company(context: moc) company3.inn = "077456789222" company3.name = " " 

Namespace untuk NSManagedObject


Hal lain yang layak disebut adalah namespace.

gambar

Anda tidak akan mengalami kesulitan jika Anda mengerjakan ObjectiveC atau Swift. Biasanya, bidang ini diisi dengan benar secara default. Tetapi dalam proyek campuran, mungkin mengejutkan Anda bahwa untuk kelas cepat dan ObjectiveC Anda perlu meletakkan opsi yang berbeda. Di Swift, "Modul" harus diisi. Jika bidang ini tidak selesai, awalan dengan nama proyek akan ditambahkan ke nama kelas, yang akan menyebabkan kesalahan runtime. Di Objetive C, biarkan "Module" kosong, jika tidak NSManagedObject tidak akan ditemukan ketika mengaksesnya melalui nama kelas.

Tautan antar objek


Pada prinsipnya, topik hubungan dibahas dengan baik, tetapi saya ingin fokus pada cara menambahkan entitas anak ke orang tua. Oleh karena itu, pertama, pengingat cepat tentang mekanisme untuk membuat tautan. Pertimbangkan contoh tradisional, perusahaan adalah karyawan, hubungan satu-ke-banyak:

  • Buat koneksi di setiap sisi (tabel)
  • Setelah itu, bidang Invers menjadi tersedia, itu harus diisi di setiap tabel.

gambar

gambar

Apple bersikeras menentukan hubungan terbalik. Pada saat yang sama, inversi tidak memperkuat koherensi, tetapi membantu Core Data melacak perubahan di kedua sisi koneksi, penting untuk caching dan memperbarui informasi.

Penting juga bahwa aturan penghapusan ditentukan dengan benar. Aturan penghapusan adalah tindakan yang akan dilakukan dengan objek ini ketika objek induk dihapus.

  • Cascade - penghapusan semua objek anak, ketika orang tua dihapus.
  • Tolak - larangan menghapus orang tua jika ada anak
  • Nullify - membatalkan referensi orang tua
  • Tidak ada tindakan - tidak ada tindakan yang ditentukan, itu akan memberikan peringatan saat kompilasi

Dalam contoh ini, ketika sebuah perusahaan dihapus, semua karyawan akan dihapus (kaskade). Ketika Anda menghapus seorang karyawan, tautan ke dia di perusahaan akan diatur ulang (pra layar)

Cara menambahkan entitas anak ke induk


1) Metode pertama adalah menambahkan melalui NSSet. Misalnya, tambahkan 2 karyawan ke perusahaan:

 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 

Metode ini nyaman untuk menginisialisasi objek atau mengisi database. Ada sedikit nuansa. Jika perusahaan sudah memiliki karyawan, dan Anda menetapkan satu set baru, maka mantan karyawan akan mengatur ulang tautan ke perusahaan, tetapi mereka tidak akan dihapus. Atau, Anda bisa mendapatkan daftar karyawan dan bekerja dengan set ini.

 let set = company.mutableSetValue(forKey: "employee") 

2) Menambahkan objek anak melalui id induk

 if let employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee.firstName = "" employee.secondName = "" employee.company = company } 

Metode kedua nyaman ketika menambahkan atau mengedit objek anak di
bentuk terpisah.

3) Menambahkan objek anak melalui metode yang dibuat secara otomatis

 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) } 

Demi kelengkapan, berguna untuk mengetahui tentang metode ini, tetapi entah bagaimana itu tidak berguna bagi saya, dan saya menghapus kode tambahan agar tidak mengacaukan proyek.

Pertanyaan Klausa Anak


Di Core Data, Anda tidak dapat membuat kueri sembarang antara data apa pun, seperti yang dapat kami lakukan dalam SQL. Namun di antara objek dependen, mudah dilacak menggunakan predikat standar. Di bawah ini adalah contoh dari kueri yang memilih semua perusahaan di mana ada seorang karyawan dengan nama yang ditentukan:

 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]() } 

Metode panggilan dalam kode akan terlihat seperti ini:

 //  ,    let companies = Company.getCompanyWithEmployee(name: "") 

Jangan gunakan bidang transit dalam kueri, nilainya tidak ditentukan pada saat kueri dieksekusi. Tidak ada kesalahan akan terjadi, tetapi hasilnya akan salah.

Mengatur atribut (bidang)


Anda mungkin memperhatikan bahwa atribut Entity memiliki beberapa opsi.
Dengan opsional, semuanya jelas dari namanya.

Opsi untuk menggunakan tipe skalar muncul di swif. Objective-C tidak menggunakan tipe skalar untuk atribut, karena mereka tidak boleh nol. Mencoba untuk menetapkan nilai skalar ke atribut melalui KVC akan menyebabkan kesalahan runtime. Ini memperjelas mengapa tipe atribut dalam Core Data tidak memiliki korespondensi yang ketat dengan jenis bahasa. Dalam cepat, dan dalam proyek campuran, atribut tipe skalar dapat digunakan.

Atribut transit adalah bidang terhitung yang tidak disimpan dalam database. Mereka dapat digunakan untuk enkripsi. Atribut-atribut ini menerima nilai melalui accessor yang diganti, atau melalui menetapkan primitif sesuai kebutuhan (misalnya, akan ditimpa Hemat dan awakeFromFetch).

Aksesor Atribut:


Jika Anda tidak perlu menggunakan bidang terhitung, misalnya, untuk melakukan enkripsi atau sesuatu yang lain, maka Anda tidak perlu memikirkan apa itu aksesori atribut. Sementara itu, operasi untuk mendapatkan dan menetapkan nilai ke atribut bukan "atom". Untuk memahami apa yang saya maksud, lihat kode di bawah ini:

 //  let name = company.name //  company.willAccessValue(forKey: "name") let name = company.primitiveValue(forKey: "name") company.didAccessValue(forKey: "name") //  company.name = " " //  company.willChangeValue(forKey: "name") company.setPrimitiveValue(" ", forKey: "name") company.didChangeValue(forKey: "name") 

Gunakan primitif dalam peristiwa NSManagedObject alih-alih tugas yang biasa untuk menghindari perulangan. Contoh:

 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() } 

Jika tiba-tiba suatu saat Anda harus mengacaukan fungsi awakeFromFetch menjadi sebuah proyek, maka Anda akan terkejut bahwa itu berfungsi sangat aneh, tetapi sebenarnya itu tidak dipanggil sama sekali ketika Anda menjalankan permintaan. Ini disebabkan oleh fakta bahwa Core Data memiliki mekanisme caching yang sangat cerdas, dan jika seleksi sudah ada dalam memori (misalnya, karena Anda baru saja mengisi tabel ini), maka metode ini tidak akan dipanggil. Namun, percobaan saya menunjukkan bahwa dalam hal nilai yang dihitung, Anda dapat dengan aman mengandalkan penggunaan awakeFromFetch, seperti yang dikatakan oleh dokumentasi Apple. Jika untuk pengujian dan pengembangan Anda perlu memaksa awakeFromFetch, tambahkan managedObjectContext.refreshAllObjects () sebelum permintaan.

Itu saja.

Terima kasih kepada semua orang yang membaca sampai akhir.

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


All Articles