Preparing the Core Data models for CloudKit

You might have noticed the following line in the code for creating a new CKRecord that was shown in the previous section:

let recordId = CKRecord.ID(recordName: UUID().uuidString, zoneID: defaultZoneId)

The recordName that is set on CKRecord.ID is the unique identifier that CloudKit uses to store records. When you want to import data from CloudKit, you can use recordName to check whether you have already saved a record in your local database. In addition to this unique identifier, CloudKit itself stores metadata about records. You saw this metadata when you added a new record type in the CloudKit dashboard and all these new properties that you didn't add yourself showed up.

Apart from the unique identifier, none of the metadata is very relevant to use in the MustC app, so adding all fields to the Core Data model would be a waste. Luckily, CKRecord has a convenient method that allows you to encode all of the automatically-added metadata into a Data object that you can easily store in Core Data. This means that you'll need to add two new properties to both the FamilyMember and Movie models:

  • recordName (String)
  • cloudKitData (Data)

Go ahead and add these two properties to your models in the Core Data model editor.

After adding the recordName and cloudKitData attributes, you should add a couple of helpers to your models. Create a new file in the Models folder and name it FamilyMember.swift. Add the following extension to this file:

extension FamilyMember {
  // 1
  func recordIDForZone(_ zone: CKRecordZone.ID) -> CKRecord.ID {
    return CKRecord.ID(recordName: self.recordName!, zoneID: zone)
  }

  // 2
  func recordForZone(_ zone: CKRecordZone.ID) -> CKRecord {
    let record: CKRecord

    // 3
    if let data = cloudKitData, let coder = try? NSKeyedUnarchiver(forReadingFrom: data) {
      coder.requiresSecureCoding = true
      record = CKRecord(coder: coder)!
    } else {
      record = CKRecord(recordType: "FamilyMember", recordID: recordIDForZone(zone))
    }

    record["name"] = name!

    // 4
    if let movies = self.movies as? Set<Movie> {
      let references: [CKRecord.Reference] = movies.map { movie in
        let movieRecord = movie.recordForZone(zone)
        return CKRecord.Reference(record: movieRecord, action: .none)
      }

      record["movies"] = references
    }

    return record
  }

  // 5
  static func find(byIdentifier recordName: String, in moc: NSManagedObjectContext) -> FamilyMember? {
    let predicate = NSPredicate(format: "recordName == %@", recordName)
    let request: NSFetchRequest<FamilyMember> = FamilyMember.fetchRequest()
    request.predicate = predicate

    guard let result = try? moc.fetch(request)
      else { return nil }

    return result.first
  }
}

A lot is going on in this snippet, so let's go over it step by step:

  1. The first comment highlights a convenient helper method that creates a record ID. This helper method receives an existing zone ID and uses the new recordName property that you just added in the Core Data model editor.
  2. This method is used to convert the FamilyMember model into a CKRecord instance. This method will be used when you send data to CloudKit.
  3. CKRecord objects can be created in different ways. One way is through the CKRecord(recordType:recordID:) initializer. You can also use an instance of NSCoder to create a CKRecord with the CKRecord(coder:) initializer. An NSCoder can convert objects into data and vice versa. So, in this case, a special version of NSCoder, called NSKeyedUnarchiver, is used to convert the metadata that is stored as Data in Core Data back into a CKRecord instance. If the Core Data object has just been added, it won't have any CloudKit metadata yet, so a new CKRecord instance should be created.
  4. To make sure all movies for the family member are sent to CloudKit, it is required to loop over each movie and create a CKRecord.Reference to the object. This list of references is then assigned to the movie record.
  5. To import CloudKit objects, the code must be able to look up existing movie records using the record's recordName. This method is used to look up family members by their record name.

The Movie object should receive similar helper methods to the ones you just added to FamilyMember. Add the following methods to the extension in Movie.swift:

func recordIDForZone(_ zone: CKRecordZone.ID) -> CKRecord.ID {
  return CKRecord.ID(recordName: self.recordName!, zoneID: zone)
}

func recordForZone(_ zone: CKRecordZone.ID) -> CKRecord {
  let record: CKRecord

  if let data = cloudKitData, let coder = try? NSKeyedUnarchiver(forReadingFrom: data) {
    coder.requiresSecureCoding = true
    record = CKRecord(coder: coder)!
  } else {
    record = CKRecord(recordType: "Movie", recordID: recordIDForZone(zone))
  }

  record["title"] = title!
  record["rating"] = popularity
  record["remoteId"] = remoteId

  return record
}

static func find(byIdentifier recordName: String, in moc: NSManagedObjectContext) -> Movie? {
  let predicate = NSPredicate(format: "recordName == %@", recordName)
  let request: NSFetchRequest<Movie> = Movie.fetchRequest()
  request.predicate = predicate

  guard let result = try? moc.fetch(request)
    else { return nil }

  return result.first
}

static func find(byIdentifiers recordNames: [String], in moc: NSManagedObjectContext) -> [Movie] {
  let predicate = NSPredicate(format: "ANY recordName IN %@", recordNames)
  let request: NSFetchRequest<Movie> = Movie.fetchRequest()
  request.predicate = predicate

  guard let result = try? moc.fetch(request)
    else { return [] }

  return result
}

The code in the preceding snippet should speak for itself. One interesting addition is the find(byIdentifiers:) method. Instead of taking just a single record name, this method takes a list of record names. When you import family members from CloudKit, a single family member could have multiple movies in their favorites. Instead of fetching each movie individually, this method allows you to retrieve all matching movies at once.

Your Core Data models are now fully compatible with CloudKit, and you're ready to write the code that will import the data from the CloudKit servers and add them to your local Core Data database.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.77.195