Data Inti dari Scratch: Hubungan dan Lebih Banyak Pengambilan
Indonesian (Bahasa Indonesia) translation by Dwi Bagus Nurrohman (you can also view the original English article)
Dalam artikel sebelumnya, kita belajar tentang NSManagedObject
dan betapa mudahnya membuat, membaca, memperbarui, dan menghapus rekaman menggunakan Data Inti. Namun, saya tidak menyebutkan hubungan dalam diskusi tersebut. Selain beberapa peringatan yang perlu Anda sadari, hubungan sama mudahnya untuk memanipulasi sebagai atribut. Dalam artikel ini, kami akan fokus pada hubungan dan kami juga akan melanjutkan eksplorasi NSFetchRequest
kami.
1. Hubungan
Kami sudah bekerja dengan hubungan dalam editor model Data Inti dan yang akan saya ceritakan kepada Anda akan terdengar akrab. Hubungan, seperti atribut, diakses menggunakan kode nilai kunci. Ingat bahwa model data yang kami buat sebelumnya dalam seri ini mendefinisikan entitas Person dan entitas Address. Seseorang terhubung dengan satu atau lebih alamat dan alamat terkait dengan satu atau lebih orang. Ini adalah hubungan banyak-ke-banyak.
Untuk mengambil alamat seseorang, kita cukup memanggil valueForKey:
pada orang itu, instance NSManagedObject
, dan masukkan addresses
sebagai kuncinya. Perhatikan bahwa addresses
adalah kunci yang kami tetapkan dalam model data. Jenis objek apa yang Anda harapkan? Kebanyakan orang yang baru menggunakan Core Data mengharapkan NSArray
yang diurutkan, tetapi Core Data mengembalikan NSSet
, yang tidak disortir. Bekerja dengan NSSet
memiliki kelebihan seperti yang akan Anda pelajari nanti.
Menciptakan Catatan
Cukup dengan teori, buka proyek dari artikel sebelumnya atau klon dari GitHub. Mari kita mulai dengan membuat seseorang lalu menautkannya ke alamat. Untuk membuat seseorang, perbarui application: didFinishLaunchingWithOptions:
metode seperti yang ditunjukkan di bawah ini.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Initialize Window self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Configure Window [self.window setBackgroundColor:[UIColor whiteColor]]; [self.window makeKeyAndVisible]; // Create Person NSEntityDescription *entityPerson = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.managedObjectContext]; NSManagedObject *newPerson = [[NSManagedObject alloc] initWithEntity:entityPerson insertIntoManagedObjectContext:self.managedObjectContext]; // Set First and Lats Name [newPerson setValue:@"Bart" forKey:@"first"]; [newPerson setValue:@"Jacobs" forKey:@"last"]; [newPerson setValue:@44 forKey:@"age"]; return YES; }
Ini seharusnya terlihat familier jika Anda telah membaca artikel sebelumnya. Membuat alamat terlihat mirip seperti yang Anda lihat di bawah ini.
// Create Address NSEntityDescription *entityAddress = [NSEntityDescription entityForName:@"Address" inManagedObjectContext:self.managedObjectContext]; NSManagedObject *newAddress = [[NSManagedObject alloc] initWithEntity:entityAddress insertIntoManagedObjectContext:self.managedObjectContext]; // Set First and Last Name [newAddress setValue:@"Main Street" forKey:@"street"]; [newAddress setValue:@"Boston" forKey:@"city"];
Karena setiap atribut dari entitas Address ditandai sebagai opsional, kita tidak perlu menetapkan nilai untuk setiap atribut. Dalam contoh di atas, kami hanya mengatur atribut catatan street
dan city
.
Menciptakan Hubungan
Untuk menautkan newAddress
ke newPerson
, kami memanggil setValue:forKey:
, meneruskan addresses
sebagai kuncinya. Nilai yang kami berikan adalah NSSet
yang berisi newAddress
. Lihatlah blok kode berikut untuk klarifikasi.
// Add Address to Person [newPerson setValue:[NSSet setWithObject:newAddress] forKey:@"addresses"]; // Save Managed Object Context NSError *error = nil; if (![newPerson.managedObjectContext save:&error]) { NSLog(@"Unable to save managed object context."); NSLog(@"%@, %@", error, error.localizedDescription); }
Kami menyebutnya save:
pada konteks objek yang dikelola objek newPerson
untuk menyebarkan perubahan ke penyimpanan persisten. Ingat bahwa panggilan save:
pada konteks objek yang dikelola menyimpan keadaan konteks objek yang dikelola. Ini berarti bahwa newAddress
juga ditulis ke backing store serta hubungan yang baru saja kita definisikan.
Anda mungkin bertanya-tanya mengapa kami tidak mengaitkan newPerson
dengan newAddress
, karena kami mendefinisikan hubungan terbalik dalam model data kami. Data Inti menciptakan hubungan ini untuk kami. Jika suatu hubungan memiliki hubungan terbalik, maka Core Data akan mengurus ini secara otomatis. Anda dapat memverifikasi ini dengan menanyakan objek newAddress
untuk persons
.
Mengambil dan Memutakhirkan Hubungan
Memperbarui sebuah hubungan tidak sulit juga. Satu-satunya peringatan adalah bahwa kita perlu menambahkan atau menghapus elemen dari NSSet
yang tidak dapat diubah, misalnya, tangan Data Core kepada kami. Untuk membuat tugas ini lebih mudah, bagaimanapun, NSManagedObject
mendeklarasikan metode kemudahan bisa mutableSetValueForKey:
, yang mengembalikan objek NSMutableSet
. Kami kemudian hanya dapat menambahkan atau menghapus item dari koleksi untuk memperbarui hubungan.
Lihatlah blok kode berikut di mana kami membuat alamat lain dan mengaitkannya dengan newPerson
. Kami melakukan ini dengan menerapkan mutableSetValueForKey:
pada newPerson
dan tambahkan otherAddress
ke set yang bisa berubah. Tidak perlu memberitahu Core Data bahwa kami telah memperbarui hubungan. Data Inti melacak kumpulan yang bisa berubah yang diberikannya dan memperbarui hubungan yang sesuai.
// Create Address NSManagedObject *otherAddress = [[NSManagedObject alloc] initWithEntity:entityAddress insertIntoManagedObjectContext:self.managedObjectContext]; // Set First and Last Name [otherAddress setValue:@"5th Avenue" forKey:@"street"]; [otherAddress setValue:@"New York" forKey:@"city"]; // Add Address to Person NSMutableSet *addresses = [newPerson mutableSetValueForKey:@"addresses"]; [addresses addObject:otherAddress];
Menghapus Hubungan
Menghapus sebuah relasi sesederhana seperti memohon setValue:forKey:
, melewati nil
sebagai nilai dan nama hubungan sebagai kuncinya. Ini membatalkan tautan setiap alamat dari newPerson
.
// Delete Relationship [newPerson setValue:nil forKey:@"addresses"];
2. Hubungan Satu-ke-Satu dan Satu-ke-Banyak
Hubungan Satu-ke-Satu
Meskipun model data kami tidak mendefinisikan hubungan satu-ke-satu, Anda telah mempelajari semua yang perlu Anda ketahui untuk bekerja dengan jenis hubungan ini. Bekerja dengan hubungan satu-ke-satu identik dengan bekerja dengan atribut. Satu-satunya perbedaan adalah bahwa nilai yang Anda dapatkan kembali dari valueForKey:
dan nilai yang Anda berikan kepada setValue:forKey:
adalah instance NSManagedObject
.
Mari perbarui model data kami untuk mengilustrasikan ini. Buka Core_Data.xcdatamodeld dan pilih entitas Person. Buat hubungan baru dan beri nama spouse. Atur entitas Person sebagai tujuan dan tetapkan hubungan spouse sebagai hubungan terbalik.



Seperti yang Anda lihat, sangat mungkin untuk menciptakan hubungan di mana tujuan hubungan adalah entitas yang sama dengan entitas yang menentukan hubungan. Juga perhatikan bahwa kami selalu mengatur kebalikan dari hubungan itu. Sebagaimana dinyatakan oleh dokumentasi, ada sangat sedikit situasi di mana Anda ingin membuat hubungan yang tidak memiliki hubungan terbalik.
Tahukah Anda apa yang akan terjadi jika Anda membangun dan menjalankan aplikasi? Itu benar, aplikasi akan crash. Karena kami mengubah model data, toko yang ada, database SQLite dalam contoh ini, tidak lagi kompatibel dengan model data. Untuk memperbaiki ini, hapus aplikasi dari perangkat Anda atau iOS Simulator dan jalankan aplikasi. Namun jangan khawatir, kami akan menyelesaikan masalah ini dengan lebih elegan dalam angsuran berikutnya menggunakan migrasi.
Jika Anda dapat menjalankan aplikasi tanpa masalah, maka saatnya untuk langkah selanjutnya. Kembali ke kelas delegasi aplikasi dan tambahkan blok kode berikut.
// Create Another Person NSManagedObject *anotherPerson = [[NSManagedObject alloc] initWithEntity:entityPerson insertIntoManagedObjectContext:self.managedObjectContext]; // Set First and Last Name [anotherPerson setValue:@"Jane" forKey:@"first"]; [anotherPerson setValue:@"Doe" forKey:@"last"]; [anotherPerson setValue:@42 forKey:@"age"];
Untuk menetapkan anotherPerson
sebagai pasangan dari newPerson
, kami mengaktifkan setValue: forKey:
pada newPerson
dan meneruskan anotherPerson
dan @"spouse"
sebagai argumen. Kita dapat mencapai hasil yang sama dengan menerapkan setValue:forKey:
pada anotherPerson
dan meneruskan newPerson
dan @"spouse"
sebagai argumen.
// Create Relationship [newPerson setValue:anotherPerson forKey:@"spouse"];
Hubungan Satu-ke-Banyak
Mari kita selesaikan dengan melihat hubungan satu ke banyak. Buka Core_Data.xcdatamodeld, pilih entitas Person, dan buat hubungan bernama children. Setel tujuan ke Person, atur jenis ke To Many, dan biarkan hubungan terbalik kosong untuk saat ini.



Buat hubungan lain bernama father, tetapkan tujuan ke Person, dan atur hubungan terbalik dengan children. Ini secara otomatis akan mengisi hubungan terbalik dari hubungan children yang kita kosongkan beberapa saat yang lalu. Kami sekarang telah menciptakan hubungan satu-ke-banyak, yaitu, seorang ayah dapat memiliki banyak anak, tetapi seorang anak hanya dapat memiliki satu ayah.
Kembali ke delegasi aplikasi dan tambahkan blok kode berikut. Kami membuat catatan Person yang lain, mengatur atributnya, dan mengaturnya sebagai anak dari newPerson
dengan meminta Data Inti untuk set yang dapat berubah untuk kunci children
dan menambahkan catatan baru ke set yang bisa berubah.
// Create a Child Person NSManagedObject *newChildPerson = [[NSManagedObject alloc] initWithEntity:entityPerson insertIntoManagedObjectContext:self.managedObjectContext]; // Set First and Last Name [newChildPerson setValue:@"Jim" forKey:@"first"]; [newChildPerson setValue:@"Doe" forKey:@"last"]; [newChildPerson setValue:@21 forKey:@"age"]; // Create Relationship NSMutableSet *children = [newPerson mutableSetValueForKey:@"children"]; [children addObject:newChildPerson];
Blok kode berikut menyelesaikan hasil yang sama dengan menetapkan atribut father
dari anotherChildPerson
. Hasilnya adalah newPerson
menjadi bapak dari otherChildPerson
dan anotherChildPerson
menjadi anak dari newPerson
.
// Create Another Child Person NSManagedObject *anotherChildPerson = [[NSManagedObject alloc] initWithEntity:entityPerson insertIntoManagedObjectContext:self.managedObjectContext]; // Set First and Last Name [anotherChildPerson setValue:@"Lucy" forKey:@"first"]; [anotherChildPerson setValue:@"Doe" forKey:@"last"]; [anotherChildPerson setValue:@19 forKey:@"age"]; // Create Relationship [anotherChildPerson setValue:newPerson forKeyPath:@"father"];
3. Lebih Banyak Mengambil
Model data dari aplikasi contoh kami telah berkembang sedikit dalam hal kompleksitas. Kami telah membuat hubungan satu-ke-satu, satu-ke-banyak, dan banyak-ke-banyak. Kami telah melihat betapa mudahnya membuat rekaman, termasuk hubungan. Namun, jika kami juga ingin dapat menarik data tersebut dari penyimpanan persisten, maka kami perlu tahu lebih banyak tentang pengambilan. Mari kita mulai dengan contoh sederhana di mana kita melihat bagaimana mengurutkan hasil yang dikembalikan oleh permintaan pengambilan.
Urutkan Deskriptor
Untuk mengurutkan catatan yang kami dapatkan dari konteks objek yang dikelola, kami menggunakan kelas NSSortDescriptor
. Lihatlah potongan kode berikut.
// Fetching NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Person"]; // Add Sort Descriptor NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"first" ascending:YES]; [fetchRequest setSortDescriptors:@[sortDescriptor]]; // Execute Fetch Request NSError *fetchError = nil; NSArray *result = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError]; if (!fetchError) { for (NSManagedObject *managedObject in result) { NSLog(@"%@, %@", [managedObject valueForKey:@"first"], [managedObject valueForKey:@"last"]); } } else { NSLog(@"Error fetching data."); NSLog(@"%@, %@", fetchError, fetchError.localizedDescription); }
Kami menginisialisasi permintaan pengambilan dengan meneruskan entitas yang kami minati, Person. Kami kemudian membuat objek NSSortDescriptor
dengan menerapkan sortDescriptorWithKey:ascending:
, meneruskan atribut dari entitas yang ingin kita urutkan berdasarkan, first
, dan boolean yang menunjukkan apakah rekaman perlu diurutkan dalam urutan menaik atau menurun.
Kami mengikat deskriptor semacam permintaan ambil dengan memanggil setSortDescriptors:
pada permintaan ambil, lewat dalam array yang mencakup deskripsi semacam. Karena setSortDescriptors:
menerima larik, adalah mungkin untuk meneruskan lebih dari satu jenis deskripsi. Kita akan melihat opsi ini di sejenak.
Sisa dari blok kode harus terlihat akrab. Permintaan pengambilan diteruskan ke konteks objek yang dikelola, yang mengeksekusi permintaan ambil saat kita menjalankan executeFetchRequest:error:
. Penting untuk selalu meneruskan pointer ke objek NSError
untuk mengetahui apa yang salah jika eksekusi permintaan pengambilan gagal.
Jalankan aplikasi dan periksa output di konsol Xcode. Outputnya akan terlihat seperti apa yang ditunjukkan di bawah ini. Seperti yang Anda lihat, catatan diurutkan berdasarkan nama depannya.
Core Data[1080:613] Bart, Jacobs Core Data[1080:613] Jane, Doe Core Data[1080:613] Jim, Doe Core Data[1080:613] Lucy, Doe
Jika Anda melihat duplikat dalam output, maka pastikan untuk mengomentari kode yang kami tulis sebelumnya untuk membuat rekaman. Setiap kali Anda menjalankan aplikasi, catatan yang sama dibuat, menghasilkan rekaman duplikat.
Seperti yang saya sebutkan sebelumnya, mungkin untuk menggabungkan beberapa jenis deskriptor. Mari kita urutkan catatan berdasarkan nama terakhir dan umur mereka. Kami pertama-tama mengatur kunci dari deskriptor tipe pertama ke last
. Kami kemudian membuat deskriptor jenis lain dengan kunci age
dan menambahkannya ke array deskripsi semacam yang kami berikan kepada setSortDescriptors
:.
// Add Sort Descriptor NSSortDescriptor *sortDescriptor1 = [NSSortDescriptor sortDescriptorWithKey:@"last" ascending:YES]; NSSortDescriptor *sortDescriptor2 = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]; [fetchRequest setSortDescriptors:@[sortDescriptor1, sortDescriptor2]];
Output menunjukkan bahwa urutan deskriptor semacam dalam array penting. Catatan-catatan ini pertama kali diurutkan berdasarkan nama belakang mereka dan kemudian berdasarkan usia mereka.
Core Data[1418:613] Lucy, Doe (19) Core Data[1418:613] Jim, Doe (21) Core Data[1418:613] Jane, Doe (42) Core Data[1418:613] Bart, Jacobs (44)
Predikat
Sortor deskripsinya bagus dan mudah digunakan, tetapi predikat adalah apa yang benar-benar membuat mengambil kuat di Core Data. Sementara deskriptor semacam memberi tahu Data Inti bagaimana catatan perlu disortir, predikat menceritakannya catatan apa yang Anda minati. Kelas yang akan kami kerjakan adalah NSPredicate
.
Mari kita mulai dengan mengambil setiap anggota keluarga Doe. Ini sangat mudah dilakukan dan sintaksnya akan mengingatkan Anda tentang SQL.
// Fetching NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Person"]; // Create Predicate NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"last", @"Doe"]; [fetchRequest setPredicate:predicate]; // Add Sort Descriptor NSSortDescriptor *sortDescriptor1 = [NSSortDescriptor sortDescriptorWithKey:@"last" ascending:YES]; NSSortDescriptor *sortDescriptor2 = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]; [fetchRequest setSortDescriptors:@[sortDescriptor1, sortDescriptor2]]; // Execute Fetch Request NSError *fetchError = nil; NSArray *result = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError]; if (!fetchError) { for (NSManagedObject *managedObject in result) { NSLog(@"%@, %@", [managedObject valueForKey:@"first"], [managedObject valueForKey:@"last"]); } } else { NSLog(@"Error fetching data."); NSLog(@"%@, %@", fetchError, fetchError.localizedDescription); }
Kami tidak banyak berubah selain membuat objek NSPredicate
dengan menerapkan predicateWithFormat:
dan mengikat predikat ke permintaan pengambilan dengan meneruskannya sebagai argumen dari panggilan setPredicate:
. Ide di balik predicateWithFormat:
mirip dengan stringWithFormat:
dalam hal ini menerima sejumlah argumen variabel.
Perhatikan bahwa string format predikat menggunakan %K
untuk nama properti dan %@
untuk nilainya. Sebagaimana dinyatakan dalam Panduan Pemrograman Predikat,%K
adalah substitusi argumen variabel untuk jalur kunci sementara%@
adalah substitusi argumen variabel untuk nilai objek. Ini berarti bahwa string format predikat dari contoh kami mengevaluasi ke last == "Doe"
.
Jika Anda menjalankan aplikasi sekali lagi dan memeriksa output di konsol Xcode, Anda akan melihat hasil berikut:
Core Data[1582:613] Lucy, Doe (19) Core Data[1582:613] Jim, Doe (21) Core Data[1582:613] Jane, Doe (42)
Ada banyak operator yang bisa kita gunakan untuk perbandingan. Selain =
dan ==
, yang identik sejauh menyangkut Data Inti, ada juga >=
dan =>
, <=
dan =>
,!=
Dan <>
, dan>
dan <
. Saya mendorong Anda untuk bereksperimen dengan operator ini untuk mempelajari bagaimana mereka memengaruhi hasil permintaan pengambilan.
Predikat berikut menggambarkan bagaimana kita dapat menggunakan >=
operator untuk hanya mengambil catatan Orang dengan atribut age
lebih dari 30
.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K >= %@", @"age", @(30)];
Kami juga memiliki operator untuk perbandingan string, CONTAINS
, LIKE
, MATCHES
, BEGINSWITH
, dan ENDSWITH
. Mari kita ambil setiap catatan Orang yang namanya CONTAINS
huruf j
.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K CONTAINS %@", @"first", @"j"];
Jika Anda menjalankan aplikasi sekarang, larik hasil akan kosong karena perbandingan string peka huruf besar secara default. Kita dapat mengubahnya dengan menambahkan pengubah seperti ini:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K CONTAINS[c] %@", @"first", @"j"];
Anda juga dapat membuat predikat gabungan menggunakan kata kunci AND
, OR
, dan NOT
. Dalam contoh berikut, kami mengambil setiap orang yang nama depannya mengandung huruf j
dan lebih muda dari 30
.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K CONTAINS[c] %@ AND %K < 30", @"first", @"j", @"age", @(30)];
Predikat juga membuatnya sangat mudah untuk mengambil catatan berdasarkan hubungan mereka. Dalam contoh berikut, kami mengambil setiap orang yang nama ayahnya sama dengan Bart
.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"father.first", @"Bart"];
Predikat di atas bekerja seperti yang diharapkan, karena%K
adalah substitusi argumen variabel untuk jalur kunci, bukan hanya kunci.
Yang perlu Anda ingat adalah predikat itu memungkinkan Anda untuk meminta informasi backing store tanpa Anda mengetahui apa pun tentang toko tersebut. Meskipun sintaks string format predikat mengingatkan pada SQL dalam beberapa hal, tidak masalah jika backing store adalah database SQLite atau penyimpanan di memori. Ini adalah konsep yang sangat kuat yang tidak unik untuk Data Inti. Rekaman Aktif Rails adalah contoh lain dari paradigma ini.
Ada lebih banyak predikat daripada apa yang saya tunjukkan dalam artikel ini. Jika Anda ingin mempelajari lebih lanjut tentang predikat, saya sarankan Anda mencapai puncak di Panduan Pemrograman Predikat Apple. Kami juga akan bekerja lebih banyak dengan predikat di beberapa artikel berikutnya dari seri ini.
Kesimpulan
Kami sekarang memiliki pemahaman yang baik tentang dasar-dasar Core Data dan saatnya untuk mulai bekerja dengan kerangka kerja dengan membuat aplikasi yang memanfaatkan kekuatannya. Pada artikel berikutnya, kami bertemu kelas penting lainnya dari kerangka Core Data, NSFetchedResultsController
. Kelas ini akan membantu kami mengelola koleksi rekaman, tetapi Anda akan belajar bahwa itu cukup lebih dari itu.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weekly