Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

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 ↗

Transcript all transcripts

Code shown on screen · 13 snippets

CloudKit: Existing convenience API swift · at 3:34 ↗
// 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 swift · at 4:04 ↗
// 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 swift · at 5:39 ↗
// 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 swift · at 6:35 ↗
// 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 swift · at 9:14 ↗
// 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 swift · at 9:37 ↗
// 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 swift · at 13:43 ↗
extension CKRecord {
    @NSCopying open var encryptedValues: CKRecordKeyValueSetting { get }
}
CloudKit: Using encrypted values swift · at 14:29 ↗
// 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 swift · at 16:35 ↗
open func accountStatus(completionHandler: @escaping (CKAccountStatus, Error?) -> Void)
CloudKit: CKAccountStatus swift · at 16:46 ↗
public enum CKAccountStatus : Int {
    case couldNotDetermine
    case available
    case restricted
    case noAccount
    case temporarilyUnavailable
}
CloudKit: Setup a record hierarchy swift · at 21:10 ↗
// 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 swift · at 21:41 ↗
// 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 swift · at 22:51 ↗
// 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