An Introduction to CloudKit Data Storage on iOS 10

From Techotopia
Revision as of 04:29, 10 November 2016 by Neil (Talk | contribs) (Data Storage and Transfer Quotas)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

PreviousTable of ContentsNext
An iOS 10 Swift Core Data TutorialAn Introduction to CloudKit Sharing


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


The CloudKit Framework is one of the more remarkable developer features available in the iOS SDK solely because of the ease with which it allows for the structured storage and retrieval of data on Apple’s iCloud database servers.

It is not an exaggeration to state that CloudKit allows developers to work with cloud based data and media storage without any prior database experience and with a minimal amount of coding effort.

This chapter will provide a high level introduction to the various elements that make up CloudKit, build a foundation for the CloudKit tutorials presented in the next two chapters and provide a basis from which to explore other capabilities of CloudKit.


Contents


An Overview of CloudKit

The CloudKit Framework provides applications with access to the iCloud servers hosted by Apple and provides an easy to use way to store, manage and retrieve data and other asset types (such as large binary files, videos and images) in a structured way. This provides a platform for users to store private data and access it from multiple devices, and also for the developer to provide data that is publicly available to all the users of an application.

The first step in learning to use CloudKit is to gain an understanding of the key components that constitute the CloudKit framework.

CloudKit Containers

Each CloudKit enabled application has at least one container on iCloud. The container for an application is represented in the CloudKit Framework by the CKContainer class and it is within these containers that the databases reside. Containers may also be shared between multiple applications.

A reference to an application’s default cloud container can be obtained via the default property of the CKContainer class:

let container = CKContainer.default

CloudKit Public Database

Each cloud container contains a single public database. This is the database into which is stored data that is needed by all users of an application. A map application, for example, might have a set of data about locations and routes that are applicable to all users of the application. This data would be stored within the public database of the application’s cloud container.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

CloudKit databases are represented within the CloudKit Framework by the CKDatabase class. A reference to the public cloud database for a container can be obtained via the publicCloudDatabase property of a container instance:

let publicDatabase = container.publicCloudDatabase

CloudKit Private Databases

Private cloud databases are used to store data that is private to each specific user. Each cloud container, therefore, will contain one private database for each user of the application. A reference to the private cloud database can be obtained via the privateCloudDatabase property of the container object:

let privateDatabase = container.privateCloudDatabase

Data Storage and Transfer Quotas

Data and assets stored in the public cloud database of an application count against the storage quota of the application. Anything stored in a private database, on the other hand, is counted against the iCloud quota of the corresponding user. Applications should, therefore, try to minimize the amount of data stored in private databases to avoid users having to unnecessarily purchase additional iCloud storage space.

At the time of writing, each application begins with 50MB of public database storage space and 5GB of space for assets free of charge. In addition, each application starts with an initial 25MB per day of free data transfer for assets and 250Kb for database data.

For each additional application user, the free storage quotas increase by 100MB and 1MB for assets and database data respectively. Data transfer quotas also increase by 0.5MB per day and 5KB per day for assets and data for each additional user. As long as these quota limits are not exceeded, the resources remain free up to a limit of 1PB for assets and 10TB for databases. Maximum data transfer quotas are 5TB per day for assets and 50GB per day for databases.

The latest quota limits can be reviewed online at:

https://developer.apple.com/icloud/documentation/cloudkit-storage/

CloudKit Records

Data is stored in both the public and private databases in the form of records. Records are represented by the CKRecord class and are essentially dictionaries of key-value pairs where keys are used to reference the data values stored in the record. A wide range of data types can be stored in a record including strings, numbers, dates, arrays, locations, data objects and references to other records. New key-value fields may be added to a record at any time without the need to perform any database restructuring.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Records in a database are categorized by a record type which must be declared when the record is created and takes the form of a string value. In practice this should be set to a meaningful value that assists in identifying the purpose of the record type. Records in a cloud database can be added, updated, queried and deleted using a range of methods provided by the CKDatabase class.

The following code demonstrates the creation of a CKRecord instance initialized with a record type of “Schools” together with three key-value pair fields:

let myRecord = CKRecord(recordType: "Schools")

myRecord.setObject("Silver Oak Elementary" as CKRecordValue?, 
					forKey: "schoolname")
myRecord.setObject("100 Oak Street" as CKRecordValue?, 
					forKey: "address")
myRecord.setObject(150 as CKRecordValue?, forKey: "studentcount")

Once created and initialized, the above record could be saved via a call to the save method of a database instance as follows:

publicDatabase.save(myRecord, completionHandler:    
    ({returnRecord, error in

                if let err = error {
                    // save operation failed
                } else {
                    // save operation succeeded
                }
    }))

The method call passes through the record to be saved and specifies a completion handler in the form of a closure expression to be called when the operation returns.

Alternatively, a group of record operations may be performed in a single transaction using the CKModifyRecordsOperation class. This class also allows timeout durations to be specified for the transaction, together with completion handlers to be called at various stages during the process. The following code, for example, uses the CKModifyRecordsOperation class to add three new records and delete two existing records in a single operation. The code also establishes timeout parameters and implements all three completion handlers. Once the modify operation object has been created and configured, it is added to the database for execution:

let modifyRecordsOperation = CKModifyRecordsOperation(
		recordsToSave: [myRecord1, myRecord2, myRecord3],
 		recordIDsToDelete: [myRecord4, myRecord5])

modifyRecordsOperation.timeoutIntervalForRequest = 10
modifyRecordsOperation.timeoutIntervalForResource = 10

modifyRecordsOperation.perRecordCompletionBlock = { record, error in
     // Called after each individual record operation completes
}

modifyRecordsOperation.perRecordProgressBlock = { record, progress in
     // Called to update the status of an indivudual operation
     // progress is a Double value indicating progress so far
}

modifyRecordsOperation.modifyRecordsCompletionBlock = { 
		records, recordIDs, error in
    // Called after all of the record operations are complete
}

privateDatabase?.add(modifyRecordsOperation) 

It is important to understand that CloudKit operations are predominantly asynchronous, enabling the calling application to continue to function while the CloudKit Framework works in the background to handle the transfer of data to and from the iCloud servers. In most cases, therefore, a call to CloudKit API methods will require that a completion handler be provided. This handler code will then be executed when the corresponding operation completes and passed results data where appropriate, or an error object in the event of a failure. Given the asynchronous nature of CloudKit operations, it is important to implement robust error handling within the completion handler.

The steps involved in creating, updating, querying and deleting records will be covered in greater detail in the next chapter entitled An iOS 10 CloudKit Example.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

The overall concept of an application cloud container, private and public databases and records can be visualized as illustrated in Figure 47-1:


A CloudKit container diagram

Figure 47-1


CloudKit Record IDs

Each CloudKit record has associated with it a unique record ID represented by the CKRecordID class. If a record ID is not specified when a record is first created, one is provided for it automatically by the CloudKit framework.

CloudKit References

CloudKit references are implemented using the CKReference class and provide a way to establish relationships between different records in a database. A reference is established by creating a CKReference instance for an originating record and assigning to it the record to which the relationship is to be targeted. The CKReference object is then stored in the originating record as a key-value pair field. A single record can contain multiple references to other records.

Once a record is configured with a reference pointing to a target record, that record is said to be owned by the target record. When the owner record is deleted, all records that refer to it are also deleted and so on down the chain of references (a concept referred to as cascading deletes).

CloudKit Assets

In addition to data, CloudKit may also be used to store larger assets such as audio or video files, large documents, binary data files or images. These assets are stored within CKAsset instances. Assets can only be stored as part of a record and it is not possible to directly store an asset in a cloud database. Once created, an asset is added to a record as just another key-value field pair. The following code, for example, demonstrates the addition of an image asset to a record:

let imageAsset = CKAsset(fileURL: imageURL)

let myRecord = CKRecord(recordType: "Vacations")

myRecord.setObject("London" as CKRecordValue?, forKey: "city")
myRecord.setObject(imageAsset as CKRecordValue?, forKey: "photo")

At the time of writing, individual CloudKit assets are limited in size to 250Mb.

Record Zones

CloudKit record zones (CKRecordZone) provide a mechanism for relating groups of records within a private database. Unless a record zone is specified when a record is saved to the cloud it is placed in the default zone of the target database. Custom zones can be added to private databases and used to organize related records and perform tasks such as writing to multiple records simultaneously in a single transaction. Each record zone has associated with it a unique record zone ID (CKRecordZoneID) which must be referenced when adding new records to a zone.

Adding a record zone to a private database involves the creation of a CKRecordZone instance initialized with the name to be assigned to the zone:

let myRecordZone = CKRecordZone(zoneName: "MyRecordZone")

The zone is then saved to the database via a call to the save method of a CKDatabase instance, passing through the CKRecordZone instance together with a completion handler to be called upon completion of the operation:

privateDatabase.save(myRecordZone, completionHandler: 
        ({returnRecord, error in
            if let err = error {
               // Zone creation failed
            } else {
                // Zone creation suceeded
            }
        }))

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Once the record zone has been established on the cloud database, records may be added to that zone by including the zone ID of the record zone when creating CKRecord instances:

var myRecord = CKRecord(recordType: "Addresses", 
                            zoneID: myRecordZone.zoneID)

When the record is subsequently saved to the database it will be associated with the designated record zone.

CloudKit Sharing

Clearly, a CloudKit record contained within the public database of an app is accessible to all users of that app. Situations might arise, however, where a user wants to share with others specific records contained within a private database. This scenario is now possible with the introduction of CloudKit sharing in iOS 10, a topic that is covered in detail in the chapters entitled An Introduction to CloudKit Sharing and An iOS CloudKit Sharing Example.

CloudKit Subscriptions

CloudKit subscriptions allow users to be notified when a change occurs within the cloud databases belonging to an installed app. Subscriptions use the standard iOS push notifications infrastructure and can be triggered based on a variety of criteria such as when records are added, updated or deleted. Notifications can also be further refined using predicates so that notifications are based on data in a record matching certain criteria. When a notification arrives, it is presented to the user in the same way as other notifications through an alert or a notification entry on the lock screen.

CloudKit subscriptions are configured using the CKSubscription class and are covered in detail in the chapter entitled An iOS 10 CloudKit Subscription Example.

Obtaining iCloud User Information

Within the scope of an application’s cloud container, each user has a unique, application specific iCloud user ID and a user info record where the user ID is used as the record ID for the user’s info record.

The record ID of the current user’s info record can be obtained via a call to the fetchUserRecordID(completionHandler:) method of the container instance. Once the record ID has been obtained, this can be used to fetch the user’s record from the cloud database:

container.fetchUserRecordID(completionHandler: {recordID,    
        error in
            if let err = error {
                // Failed to get record ID
            } else {
		   // Success – fetch the user’s record here
	      }

The record is of type CKRecordTypeUserRecord and is initially empty. Once fetched, it can be used to store data in the same way as any other CloudKit record.

CloudKit can also be used to perform user discovery. This allows the application to obtain an array of the users in the current user’s address book who have also used the app. In order for the user’s information to be provided, the user must have run the app and opted in to provide the information. User discovery is performed via a call to the discoverAllIdentities(completionHandler:) method of the container instance.

The discovered data is provided in the form of an array of CKApplicationUserInfo objects which contain the user’s iCloud ID, first name and last name. The following code fragment, for example, performs a user discovery operation and outputs to the console the first and last names of any users that meet the requirements for discoverability:

container.discoverAllIdentities(completionHandler: (
     {users, error in

        if let err = error {
            print("discovery failed %@", 
                   err.localizedDescription)
        } else {

            for userInfo in user {
                let userRecordID = userInfo.userRecordID
                print("First Name = %@", userInfo.firstName)
                print("Last Name = %@", userInfo.lastName)
            }
        }
    })

CloudKit Dashboard

The CloudKit Dashboard is a web based portal that provides an interface for managing the CloudKit options and storage for applications. The dashboard can be accessed via the https://icloud.developer.apple.com/dashboard/ URL or using the CloudKit Dashboard button located in the iCloud section of the Xcode Capabilities panel for a project as shown in Figure 47-2:


Accessing the CloudKit Dashboard

Figure 47-2


Access to the dashboard requires a valid Apple developer login and password and, once loaded into a browser window, will appear as illustrated in Figure 47-3:

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

The CLoudKit Dashboard

Figure 47-3


Among other features, the dashboard provides the ability to view data, add, update, query and delete records, modify database schema, view subscriptions and configure new security roles. It also provides an interface for migrating data from a development environment over to a production environment in preparation for an application to go live in the Apple App Store.

The Performance option located in the left-hand navigation bar is a new feature in the CloudKit dashboard which provides an overview of CloudKit usage by the currently selected app, including operations performed per second, average data request size and error frequency:


The performance page of the CloudKit Dashboard

Figure 47-4


In the case of data access through the CloudKit Dashboard, it is important to be aware that private user data cannot be accessed using the dashboard interface. Only data stored in the public database and the private databases belonging to the developer account used to log into the dashboard can be viewed and modified.

Summary

This chapter has covered a number of the key classes and elements that make up the data storage features of the CloudKit framework. Each application has its own cloud container which, in turn, contains a single public cloud database in addition to one private database for each application user. Data is stored in databases in the form of records using key-value pair fields. Larger data such as videos and photos are stored as assets which, in turn, are stored as fields in records. Records stored in private databases can be grouped together into record zones and records may be associated with each other through the creation of relationships. Each application user has an iCloud user id and a corresponding user record both of which can be obtained using the CloudKit framework. In addition, CloudKit user discovery can be used to obtain, subject to permission having been given, a list of IDs for those users in the current user’s address book who have also installed and run the app.

Finally, the CloudKit Dashboard is a web based portal that provides an interface for managing the CloudKit options and storage for applications.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
An iOS 10 Swift Core Data TutorialAn Introduction to CloudKit Sharing