Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Core Data
Code

Data Inti dari Scratch: Konkurensi

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Core Data from Scratch.
Core Data from Scratch: Subclassing NSManagedObject
iOS 8: Core Data and Batch Updates

Indonesian (Bahasa Indonesia) translation by Hana Fransiska (you can also view the original English article)

Jika Anda mengembangkan aplikasi kecil atau sederhana, maka Anda mungkin tidak melihat manfaat menjalankan operasi Core Data di latar belakang. Namun, apa yang akan terjadi jika Anda mengimpor ratusan atau ribuan rekaman pada utas utama selama peluncuran pertama aplikasi Anda? Konsekuensinya bisa dramatis. Misalnya, aplikasi Anda dapat dibunuh oleh pengawas Apple karena terlalu lama untuk diluncurkan.

Dalam artikel ini, kita melihat bahaya ketika menggunakan Core Data pada beberapa utas dan kami menjelajahi beberapa solusi untuk mengatasi masalah.

1. Thread Keamanan

Ketika bekerja dengan Core Data, penting untuk selalu mengingat bahwa Core Data tidak aman. Core Data mengharapkan untuk dijalankan pada satu utas. Ini tidak berarti bahwa setiap operasi Core Data perlu dilakukan pada utas utama, yang benar untuk UIKit, tetapi itu berarti bahwa Anda perlu menyadari operasi mana yang dieksekusi pada untaian mana. Ini juga berarti bahwa Anda perlu berhati-hati bagaimana perubahan dari satu utas disebarkan ke utas lainnya.

Bekerja dengan Data Inti pada beberapa utas sebenarnya sangat sederhana dari sudut pandang teoretis. NSManagedObject, NSManagedObjectContext, dan NSPersistentStoreCoordinator bukanlah thread safe, dan instance kelas-kelas ini hanya dapat diakses dari thread yang dibuat. Seperti yang Anda bayangkan, ini menjadi sedikit lebih kompleks dalam praktiknya.

NSManagedObject

Kita sudah tahu bahwa kelas NSManagedObject tidak aman, tetapi bagaimana Anda mengakses rekaman dari untaian yang berbeda? Instance NSManagedObject memiliki metode objectID yang mengembalikan instance kelas NSManagedObjectID. Kelas NSManagedObjectID aman dan sebuah instance berisi semua informasi yang diperlukan oleh konteks objek yang dikelola untuk mengambil objek terkelola yang sesuai.

Dalam potongan kode berikut, kami meminta konteks objek yang dikelola untuk objek yang dikelola yang sesuai dengan objectID. ObjectWithID: dan existingObjectWithID: error: metode mengembalikan versi lokal — lokal ke utas terkini — dari objek terkelola yang sesuai.

Aturan dasar yang harus diingat adalah tidak melewatkan instance NSManagedObject dari satu utas ke yang lain. Sebagai gantinya, berikan objectID objek yang dikelola dan tanyakan konteks objek yang dikelola untaian untuk versi lokal dari objek yang dikelola.

NSManagedObjectContext

Karena kelas NSManagedObjectContext tidak aman, kita dapat membuat konteks objek terkelola untuk setiap utas yang berinteraksi dengan Data Inti. Strategi ini sering disebut sebagai thread confinement.

Pendekatan umum adalah menyimpan konteks objek yang dikelola dalam kamus ulir, kamus untuk menyimpan data khusus thread. Lihatlah contoh berikut untuk melihat bagaimana ini bekerja dalam praktek.

NSPersistentStoreCoordinator

Bagaimana dengan koordinator toko yang gigih? Apakah Anda perlu membuat koordinator toko persisten terpisah untuk setiap utas. Meskipun ini mungkin dan salah satu strategi yang digunakan Apple untuk merekomendasikan, itu tidak perlu.

Kelas NSPersistentStoreCoordinator dirancang untuk mendukung beberapa konteks objek yang dikelola, bahkan jika konteks objek yang dikelola dibuat pada utas yang berbeda. Karena kelas NSManagedObjectContext mengunci koordinator toko tetap saat mengaksesnya, adalah mungkin untuk beberapa konteks objek yang dikelola untuk menggunakan koordinator toko persisten yang sama bahkan jika konteks objek yang dikelola tersebut hidup pada utas yang berbeda. Hal ini membuat pengaturan Core Data multithreaded jauh lebih mudah dikelola dan kurang kompleks.

2. Strategi Concurrency

Sejauh ini, kami telah belajar bahwa Anda memerlukan beberapa konteks objek yang dikelola jika Anda melakukan operasi Core Data pada beberapa utas. Peringatannya, bagaimanapun, adalah bahwa konteks objek yang dikelola tidak menyadari keberadaan satu sama lain. Perubahan yang dilakukan pada objek yang dikelola dalam satu konteks objek yang dikelola tidak secara otomatis disebarkan ke konteks objek terkelola lainnya. Bagaimana kita mengatasi masalah ini?

Ada dua strategi populer yang direkomendasikan Apple, pemberitahuan dan konteks objek yang dikelola orang tua-anak. Mari kita lihat setiap strategi dan selidiki pro dan kontra mereka.

Skenario yang akan kita ambil sebagai contoh adalah subclass NSOperation yang bekerja di latar belakang dan mengakses Core Data pada thread latar belakang operasi. Contoh ini akan menunjukkan perbedaan dan kelebihan masing-masing strategi.

Strategi 1: Notifikasi

Sebelumnya dalam seri ini, saya memperkenalkan Anda ke kelas NSFetchedResultsController dan Anda belajar bahwa konteks objek yang dikelola memposting tiga jenis pemberitahuan:

  • NSManagedObjectContextObjectsDidChangeNotification: pemberitahuan ini diposting ketika salah satu objek yang dikelola dari konteks objek yang dikelola telah berubah
  • NSManagedObjectContextWillSaveNotification: pemberitahuan ini diposting sebelum konteks objek terkelola melakukan operasi penyimpanan
  • NSManagedObjectContextDidSaveNotification: pemberitahuan ini diposting setelah konteks objek terkelola melakukan operasi penyimpanan

Ketika konteks objek yang dikelola menyimpan perubahannya ke penyimpanan persisten, melalui koordinator toko persisten, konteks objek terkelola lainnya mungkin ingin tahu tentang perubahan tersebut. Ini sangat mudah dilakukan dan bahkan lebih mudah untuk menyertakan atau menggabungkan perubahan ke dalam konteks objek terkelola yang lain. Mari kita bicara kode.

Kami membuat operasi non-konkuren yang melakukan beberapa pekerjaan di latar belakang dan membutuhkan akses ke Data Inti. Header akan terlihat mirip dengan yang ditunjukkan di bawah ini.

Antarmuka operasi sangat sederhana karena hanya berisi properti untuk konteks objek terkelola aplikasi utama. Ada beberapa alasan untuk menjaga referensi ke konteks objek utama yang dikelola aplikasi. Ini menjadi jelas ketika kami memeriksa implementasi kelas TSPImportOperation.

Kami pertama kali menyatakan properti pribadi, privateManagedObjectContext, dari jenis NSManagedObjectContext. Ini adalah konteks objek terkelola yang akan digunakan operasi secara internal untuk melakukan tugas-tugas Data Inti.

Karena kami menerapkan subclass NSOperation non-konkuren, kami perlu menerapkan metode main. Ini seperti apa rupanya.

Ada beberapa detail penting yang perlu diklarifikasi. Kami menginisialisasi konteks objek yang dikelola pribadi dan mengatur properti koordinator toko gigih menggunakan objek mainManagedObjectContext. Ini baik-baik saja, karena kami tidak mengakses mainManagedObjectContext, kami hanya meminta referensi untuk koordinator toko gigih aplikasi. Kami tidak melanggar aturan kurungan benang.

Sangat penting untuk menginisialisasi konteks objek yang dikelola secara pribadi dalam metode main operasi, karena metode ini dijalankan pada thread latar belakang di mana operasi berjalan. Tidak bisakah kita menginisialisasi konteks objek yang dikelola dalam metode init operasi? Jawabannya adalah tidak. Metode init operasi dijalankan di thread tempat TSPImportOperation diinisialisasi, yang kemungkinan besar merupakan utas utama. Ini akan mengalahkan tujuan dari konteks objek yang dikelola secara pribadi.

Dalam metode utama operasi, kami menambahkan contoh TSPImportOperation sebagai pengamat dari notifikasi NSManagedObjectContextDidSaveNotification yang diposkan oleh konteks objek yang dikelola pribadi.

Kami kemudian melakukan pekerjaan operasi diciptakan untuk dan menyimpan perubahan konteks objek yang dikelola pribadi, yang akan memicu pemberitahuan NSManagedObjectContextDidSaveNotification. Mari kita lihat apa yang terjadi di metode managedObjectContextDidSave:.

Seperti yang Anda lihat, implementasinya pendek dan sederhana. Kami memanggil mergeChangesFromContextDidSaveNotification: pada konteks objek yang dikelola utama, meneruskan objek pemberitahuan. Seperti yang saya sebutkan sebelumnya, pemberitahuan berisi perubahan, sisipan, pembaruan, dan penghapusan, dari konteks objek yang dikelola secara pribadi. Ini adalah kunci untuk memanggil metode ini pada utas konteks objek terkelola utama yang dibuat pada, utas utama. Itu sebabnya kami mengirim panggilan ini ke utas utama.

Menempatkan kelas TSPImportOperation untuk digunakan semudah menginisialisasi instance, menyetel properti mainManagedObjectContext, dan menambahkan operasi ke antrian operasi.

Strategi 2: Konteks Objek Induk / Anak yang Dikelola

Sejak iOS 6, ada strategi yang lebih baik dan lebih elegan. Mari meninjau kembali kelas TSPImportOperation dan memanfaatkan konteks objek induk / anak yang dikelola. Konsep di balik konteks objek induk / anak dikelola sederhana namun kuat. Biarkan saya menjelaskan cara kerjanya.

Konteks objek yang dikelola anak bergantung pada konteks objek yang dikelola induknya untuk menyimpan perubahannya ke toko persisten yang sesuai. Faktanya, konteks objek yang dikelola anak tidak memiliki akses ke koordinator toko yang gigih. Setiap kali konteks objek yang dikelola anak disimpan, perubahan yang dikandungnya didorong ke konteks objek yang dikelola induk. Tidak perlu menggunakan pemberitahuan untuk menggabungkan perubahan secara manual ke dalam konteks objek utama atau induk yang dikelola.

Manfaat lain adalah kinerja. Karena konteks objek yang dikelola anak tidak memiliki akses ke koordinator toko persisten, perubahan tidak didorong ke yang terakhir ketika konteks objek yang dikelola anak disimpan. Sebaliknya, perubahan didorong ke konteks objek induk yang dikelola, mengotori itu. Perubahan tidak secara otomatis disebarkan ke koordinator toko persisten.

Konteks objek yang dikelola dapat diulang. Konteks objek yang dikelola anak dapat memiliki konteks objek yang dikelola anak sendiri. Aturan yang sama berlaku. Namun, penting untuk diingat bahwa perubahan yang didorong ke konteks objek yang dikelola induk tidak diturunkan ke konteks objek yang dikelola anak lainnya. Jika anak A mendorong perubahannya ke induknya, maka anak B tidak menyadari perubahan ini.

Membuat konteks objek yang dikelola anak sedikit berbeda dari apa yang telah kita lihat sejauh ini. Konteks objek yang dikelola anak menggunakan penginisialisasi yang berbeda, initWithConcurrencyType :. Jenis konkurensi yang diterima penginisialisasi menentukan model threading konteks objek yang dikelola. Mari kita lihat setiap tipe konkurensi.

  • NSMainQueueConcurrencyType: Konteks objek yang dikelola hanya dapat diakses dari utas utama. Pengecualian diberikan jika Anda mencoba mengaksesnya dari utas lainnya.
  • NSPrivateQueueConcurrencyType: Saat membuat konteks objek yang dikelola dengan jenis konkurensi NSPrivateQueueConcurrencyType, konteks objek yang dikelola dikaitkan dengan antrian pribadi dan hanya dapat diakses dari antrian pribadi tersebut.
  • NSConfinementConcurrencyType: Ini adalah jenis konkurensi yang sesuai dengan konsep penguraian benang yang kami jelajahi sebelumnya. Jika Anda membuat konteks objek yang dikelola menggunakan metode init, jenis konkurensi dari konteks objek yang dikelola adalah NSConfinementConcurrencyType.

Ada dua metode utama yang ditambahkan ke kerangka Core Data ketika Apple memperkenalkan konteks objek induk / anak yang dikelola, performBlock: dan performBlockAndWait :. Kedua metode itu akan membuat hidup Anda lebih mudah. Saat Anda memanggil performBlock: pada konteks objek yang dikelola dan meneruskan blok kode untuk dieksekusi, Core Data memastikan bahwa blok tersebut dieksekusi pada utas yang benar. Dalam kasus jenis NSPrivateQueueConcurrencyType konkurensi, ini berarti bahwa blok dijalankan pada antrian pribadi dari konteks objek yang dikelola.

Perbedaan antara performBlock: dan performBlockAndWait: sederhana. Metode performBlock: tidak memblokir utas saat ini. Ini menerima blok, menjadwalkannya untuk eksekusi pada antrian yang benar, dan berlanjut dengan eksekusi pernyataan berikutnya.

Metode performBlockAndWait:, bagaimanapun, memblokir. Thread dari mana performBlockAndWait: dipanggil menunggu blok yang dilewatkan ke metode untuk menyelesaikan sebelum mengeksekusi pernyataan berikutnya. Keuntungannya adalah bahwa panggilan bertingkat untuk performBlockAndWait: dijalankan secara berurutan.

Untuk mengakhiri artikel ini, saya ingin refactor kelas TSPImportOperation untuk mengambil keuntungan dari konteks objek induk / anak yang dikelola. Anda akan segera menyadari bahwa itu sangat menyederhanakan subclass TSPImportOperation.

Header tetap tidak berubah, tetapi metode main sedikit berubah. Lihatlah penerapannya yang diperbarui di bawah ini.

Itu dia. Konteks objek terkelola utama adalah induk dari konteks objek yang dikelola secara pribadi. Perhatikan bahwa kami tidak menetapkan properti persistentStoreCoordinator dari konteks objek yang dikelola pribadi dan kami tidak menambahkan operasi sebagai pengamat untuk pemberitahuan NSManagedObjectContextDidSaveNotification. Ketika konteks objek yang dikelola pribadi disimpan, perubahan secara otomatis didorong ke konteks objek induk yang dikelola. Data Inti memastikan bahwa ini terjadi pada utas yang benar. Terserah konteks objek terkelola utama, konteks objek yang dikelola induk, untuk mendorong perubahan ke koordinator toko yang gigih.

Kesimpuan

Concurrency tidak mudah dipahami atau diterapkan, tetapi naif untuk berpikir bahwa Anda tidak akan pernah menemukan situasi di mana Anda perlu melakukan operasi Core Data pada utas latar belakang.

Dalam dua artikel berikutnya, saya akan memberi tahu Anda tentang iOS 8 dan Data Inti. Apple memperkenalkan sejumlah API baru di iOS 8 dan OS X 10.10, termasuk pembaruan batch dan pengambilan asynchronous.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.