
This video is only available to subscribers. Start a subscription today to get access to this and 472 other videos.
Extracting CKRecordWrapper
This episode is part of a series: Hello CloudKit.
1. Hello Cloud Kit - Part 1 10 min |
2. Hello Cloud Kit - Part 2 10 min |
3. CloudKit Querying 12 min |
4. CloudKit References 7 min |
5. Fetching and Saving References 15 min |
6. Working with Images 15 min |
7. Fetching Paged Images 8 min |
8. CKRecord Upload Progress 6 min |
9. Isolating CloudKit from your Controllers 16 min |
10. Extracting CKRecordWrapper 7 min |
11. CloudKit Notes Manager 11 min |
12. CloudKit Notes Manager Continued 14 min |
Episode Links
Extracting a Protocol
We can extract our common structure into a protocol:
protocol CKRecordWrapper {
static var RecordType: String { get }
var record: CKRecord { get }
associatedtype FieldKey : RawRepresentable
var identifier: String? { get }
var createdAt: Date? { get }
var modifiedAt: Date? { get }
init(record: CKRecord)
}
This ensures that conforming types have a backing record, have a way to define strongly typed field keys (that have a rawValue
) and that can initialize from an existing CKRecord
.
Providing Helpful Default Implementations
We can go one step further and provide helpful default implementations for many of the protocol's requirements. For instance, the metadata properties can simply come from the backing record
:
extension CKRecordWrapper {
var identifier: String? {
return record.recordID.recordName
}
var createdAt: Date? {
return record.creationDate
}
var modifiedAt: Date? {
return record.modificationDate
}
}
Strongly Typed Reader/Writer methods
We can further extend the protocol in the specific case where the FieldKey type has a string raw value. To express this, we can add a where clause to the extension:
extension CKRecordWrapper where FieldKey.RawValue == String {
// ...
}
Then we can move our helper functions into the extension:
func getField<T>(_ key: FieldKey) -> T? {
return record[key.rawValue] as? T
}
func setField<T>(_ key: FieldKey, value: T?) {
return record[key.rawValue] = value as? CKRecordValue
}
func getReference(_ key: FieldKey) -> String? {
let ref: CKReference? = getField(key)
return ref?.recordID.recordName
}
func setReference(_ key: FieldKey, referenceIdentifier: String?) {
let ref = referenceIdentifier.flatMap { (id: String) -> CKReference in
let rid = CKRecordID(recordName: id)
return CKReference(recordID: rid, action: .deleteSelf)
}
setField(key, value: ref)
}
As long as our model types provide a string-backed enum, we'll get this implementation for free.
Updating CloudKitNote
The models get a lot cleaner:
class CloudKitNote : Note, CKRecordWrapper {
static let RecordType = "Note"
enum FieldKey : String {
case content
case folder
}
let record: CKRecord
// ... other initializers
required init(record: CKRecord) {
self.record = record
}
var content: String {
get {
return getField(.content) ?? ""
}
set {
setField(.content, value: newValue)
}
}
var folderIdentifier: String? {
get {
return getReference(.folder)
}
set {
setReference(.folder, referenceIdentifier: newValue)
}
}
Updating CloudKitFolder
Similarly, the folder type gets cleaner:
class CloudKitFolder : Folder, CKRecordWrapper {
static let RecordType = "Folder"
enum FieldKey : String {
case name
}
let record: CKRecord
var name: String {
get { return getField(.name) ?? "" }
set { setField(.name, value: newValue) }
}
required init(name: String) {
self.record = CKRecord(recordType: CloudKitFolder.RecordType)
self.name = name
}
init(folder: Folder) {
self.record = CKRecord(recordType: CloudKitFolder.RecordType)
self.name = folder.name
}
required init(record: CKRecord) {
self.record = record
}
}
With this wrapper protocol and extension, we have provided some very helpful default behavior we can leverage when making CloudKit based model types.