2021 EssentialsPrivacy & SecuritySystem Services
WWDC21 · 27 min · Essentials / Privacy & Security / System Services
What’s new in CloudKit
CloudKit provides a secure, convenient, and reliable cloud database for your apps — and it’s only getting better. Discover how you can unravel your threads with support for async/await and convenience API additions. We’ll also show you how to encourage collaboration between people using your app through sharing entire record zones of data, and explore how to adopt CloudKit features like encrypted values and help protect sensitive data within your app. To get the most out of this session, we recommend being familiar with CloudKit and its operations on containers, as well as a basic understanding of record and data types.
Watch at developer.apple.com ↗Code shown on screen · 13 snippets
CloudKit: Existing convenience API
// Sample code using existing Convenience API
/// Delete the last person record.
/// - Parameter completionHandler: An optional handler to process completion `success` or `failure`.
func deleteLastPerson(completionHandler: ((Result<Void, Error>) -> Void)? = nil) {
database.delete(withRecordID: lastPersonRecordId) { recordId, error in
if let recordId = recordId {
os_log("Record with ID \(recordId.recordName) was deleted.")
}
if let error = error {
self.reportError(error)
// If there is a completion handler, pass along the error here.
completionHandler?(.failure(error))
} else {
// If there is a completion handler, like during tests, call it back now.
completionHandler?(.success(()))
}
}
} CloudKit: Async convenience API
// Sample code updated to CloudKit Async API
/// Delete the last person record.
func deleteLastPerson() async throws {
do {
let recordId = try await database.deleteRecord(with: lastPersonRecordId)
os_log("Record with ID \(recordId.recordName) was deleted.")
} catch {
self.reportError(error)
throw error
}
} CloudKit: Existing completion blocks
// Error reporting in CKFetchRecordsOperation
extension CKFetchRecordsOperation {
var perRecordCompletionBlock: ((CKRecord?, CKRecord.ID?, Error?) -> Void)?
var fetchRecordsCompletionBlock: (([CKRecord.ID : CKRecord]?, Error?) -> Void)?
}
fetchRecordsOp.perRecordCompletionBlock = { record, recordID, error in
// error is CKError.unknownItem.
}
fetchRecordsOp.fetchRecordsCompletionBlock = { recordsByRecordID, operationError in
// operationError is CKError.partialFailure.
// operationError.partialErrorsByItemID[missingRecordID] is CKError.unknownItem.
} CloudKit: Result type completion blocks
// Error reporting in CKFetchRecordsOperation
extension CKFetchRecordsOperation {
var perRecordResultBlock: ((CKRecord.ID, Result<CKRecord, Error>) -> Void)?
var fetchRecordsResultBlock: ((Result<Void, Error>) -> Void)?
}
fetchRecordsOp.perRecordResultBlock = { recordID, result in
// result is .failure(CKError.unknownItem) or .success(record).
}
fetchRecordsOp.fetchRecordsResultBlock = { result in
// result is .success.
} CloudKit: Delete single item
// Single item delete
func deleteLastPerson() async throws {
do {
let recordId = try await database.deleteRecord(with: lastPersonRecordId)
os_log("Record with ID \(recordId.recordName) was deleted.")
} catch {
self.reportError(error)
throw error
}
} CloudKit: Delete batch
// Batched modifications
func deleteLastPeople() async throws {
do {
let recordIds = [lastPersonRecordId, penultimatePersonRecordId]
let (_, deleteResults) = try await database.modifyRecords(deleting: recordIds)
for (recordId, deleteResult) in deleteResults {
switch deleteResult {
case .failure(let error):
self.reportError(error, itemId: recordId)
case .success:
os_log("Record with ID \(recordId.recordName) was deleted.")
}
}
} catch let operationError {
self.reportError(operationError)
throw operationError
}
} CloudKit: Encrypted values
extension CKRecord {
@NSCopying open var encryptedValues: CKRecordKeyValueSetting { get }
} CloudKit: Using encrypted values
// Device 1: Encrypt data before calling CKModifyRecordsOperation.
myRecord.encryptedValues["encryptedStringField"] = "Sensitive value"
// Device 2: Decrypt data after calling CKFetchRecordsOperation.
let decryptedString = myRecord.encryptedValues["encryptedStringField"] as? String CloudKit: Account status
open func accountStatus(completionHandler: @escaping (CKAccountStatus, Error?) -> Void) CloudKit: CKAccountStatus
public enum CKAccountStatus : Int {
case couldNotDetermine
case available
case restricted
case noAccount
case temporarilyUnavailable
} CloudKit: Setup a record hierarchy
// Share a record hierarchy
let zone = CKRecordZone(zoneName: "MyZone")
// Save zone...
let fileRecordA = CKRecord(recordType: "File", recordID: CKRecord.ID(zoneID: zone.zoneID))
let fileRecordB = CKRecord(recordType: "File", recordID: CKRecord.ID(zoneID: zone.zoneID))
let folderRecord = CKRecord(recordType: "Folder", recordID: CKRecord.ID(zoneID: zone.zoneID))
fileRecordA.setParent(folderRecord)
fileRecordB.setParent(folderRecord)
// Save records... CloudKit: Record Hierarchy, Share
// Share a record hierarchy
let share = CKShare(rootRecord: folderRecord)
do {
let (saveResults, _) = try await database.modifyRecords(saving: [folderRecord, share])
for (recordID, saveResult) in saveResults {
// Handle per-record result.
}
} catch let operationError {
// Handle operation error.
} CloudKit: Share a Record Zone
// Share a record zone
let zone = CKRecordZone(zoneName: "MyZone")
// Save zone...
let share = CKShare(recordZoneID: zone.zoneID)
do {
let (saveResults, _) = try await database.modifyRecords(saving: [share])
for (recordID, saveResult) in saveResults {
// Handle per-record result.
}
} catch let operationError {
// Handle operation error.
} Resources
Related sessions
-
33 min -
24 min -
25 min -
7 min -
34 min