You have already written most of the code to retrieve changed records from the CloudKit database. The next step is to implement the code that takes the CKRecord instances that you receive from CloudKit and convert them into the correct Core Data models. Before you implement the import methods, update fetchRecordZoneChangesCompletionBlock in fetchZoneChangesInZones(_:_:) on CloudStore as follows:
let backgroundContext = persistentContainer.newBackgroundContext() operation.fetchRecordZoneChangesCompletionBlock = { [weak self] error in for record in changedMovies { self?.importMovie(withRecord: record, withContext: backgroundContext) } for record in changedFamilyMembers { self?.importFamilyMember(withRecord: record, withContext: backgroundContext) } completionHandler(error) }
The preceding snippet uses persistentContainer to obtain a background managed object context. This type of managed object context runs off the main thread, making it ideal for operations that could take a while. Since you can't ever be sure about the amount of data that CloudKit sends your way, it's a good idea to perform all import work on a background managed object context. When using background contexts, it's important that you pay extra attention to the Core Data objects you're working with. You should always make sure to create and use Core Data objects on a single thread. You can't create an object in one managed object context and then save it in the other. If you do accidentally mix up threads, you could end up with some very difficult-to-debug crashes.
Inside of the completion block, two new methods are called: one that imports movies and one that imports family members. The reason they are called in this order is to make sure all movies are imported first and the family members second. This ensures that all the family member's favorite movies exist in the database so they can be associated with the family member immediately.
Add the following extension to CloudStore.swift to implement the importers:
extension CloudStore { func importMovie(withRecord record: CKRecord, withContext moc: NSManagedObjectContext) { moc.persist { let identifier = record.recordID.recordName let movie = Movie.find(byIdentifier: identifier, in: moc) ?? Movie(context: moc) movie.title = record["title"] ?? "unkown-title" movie.remoteId = record["remoteId"] ?? 0 movie.popularity = record["rating"] ?? 0.0 movie.cloudKitData = record.encodedSystemFields movie.recordName = identifier } } func importFamilyMember(withRecord record: CKRecord, withContext moc: NSManagedObjectContext) { moc.persist { let identifier = record.recordID.recordName let familyMember = FamilyMember.find(byIdentifier: identifier, in: moc) ?? FamilyMember(context: moc) familyMember.name = record["name"] ?? "unkown-name" familyMember.cloudKitData = record.encodedSystemFields familyMember.recordName = identifier if let movieReferences = record["movies"] as? [CKRecord.Reference] { let movieIds = movieReferences.map { reference in return reference.recordID.recordName } familyMember.movies = NSSet(array: Movie.find(byIdentifiers: movieIds, in: moc)) } } } }
The first method in this extension imports movie objects. If you look at this method carefully, you'll find that it's relatively straightforward. The record's unique identifier is used to look up a movie in Core Data. If no movie could be found, a new one is created. All the following steps extract data from CKRecord and apply it to the Movie instance. Since the all code is wrapped in a moc.persist closure, the new movie will be saved immediately.
The method to import family members is slightly more complex. The basics are the same as they are for the movie import. However, the family member importer has to extract the references to movie records and then map the references to record names. These record names are then used to look up all movies in the list of favorites for this family member, and then assigns this list to the family member.
Try deleting and reinstalling the app now. You should automatically see the list of family members appear.
Just one more step until you have a complete CloudKit-enabled application that sends Core Data models to CloudKit.