© Bruce Wade 2016

Bruce Wade, OS X App Development with CloudKit and Swift, 10.1007/978-1-4842-1880-8_4

4. Introduction to CloudKit

Bruce Wade

(1)Suite No. 1408, North Vancouver, British Columbia, Canada

Now that we know what data our app must store, we will take a look at CloudKit and how it can help meet our data-storage needs. In this chapter we will provide an overview of CloudKit at a high level and provide a walkthrough of the CloudKit Dashboard. This is where we will manage the application’s data, configure access rights, and monitor resource usage.

iCloud Accounts

iCloud accounts are the backbone of CloudKit. Anything you do requires a logged-in account, with the exception of browsing public data (which will be discussed later). This gives us the benefit of having access to hundreds of millions of users without needing to develop our own authentication layer. In addition, iCloud accounts and CloudKit work seamlessly together, so if the end user is already logged in to their iCloud account they will not be asked to log in to the app. They can just start using it and creating records.

Containers

CloudKitlike most Apple technologiesrevolves around the concept of containers. Containers provide a way for apps to be uniquely identified. With CloudKit, you always have one default container; additionally, your app can access other containers to which you give it access. Being able to access additional containers enables the sharing of data between apps, so we can have an OS X admin/management app to control business-related data and a public iOS app that accesses the data as read only. The default container is the same thing as the bundle identifier. It is very important to remember that once a CloudKit container is created it cannot be deleted, so make sure your bundle identifier is what you plan to use permanently. Containers must be unique across all developers. A good practice while you are learning CloudKit is to pick one identifier you will use for all your test apps. Then, whenever you want to create a new app using that identifier, reset the development environment through the CloudKit Dashboard. (We will cover this later.)

Think of a container as a sandbox that only your app can access. Every app that uses CloudKit has its own sandbox. This prevents apps from overriding each other’s data, and ensures your data stays secure and other developers cannot ever get access to it.

Containers are exposed as a CKContainer:

let container = CKContainer.defaultContainer()              

Databases

CloudKit provides us with access to two different databases , a public database that every client app has access to, and a private database that only the account that is being used to access CloudKit can access. It is important to remember that we as developers cannot access the private database of an end user. Public database resource costs go against the developer’s quota, whereas the private database resource costs go against the end user’s iCloud data.

In the case of the Parks app, we will be storing our data in the public database so other users are able to view it. Although we won’t be using the private database in this book, we should go over some use cases to give you an example of when to use the private rather than the public database.

If we wanted to add functionality for the user to create private notes about a given parkfor warnings or reminders to themselveswe would store this information in the user’s private database, as other members don’t need to know about these. However, if we wanted to create an event on a specific date for a park meetup, that information would go in the public database so other members could view it and join.

Databases are exposed as a CKDatabase. You get access to the databases through the CKContainer:

let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase

You can create access roles and permissions for public databases in the CloudKit Dashboard.

Records

A CKRecord is structure data that wraps key-value pairs, where each value has a record type. A record in the developer mode uses a just-in-time schema. When you move to production, the data is not used as just-in-time. This means while you are developing your app you can change your data structure as much as you want. However once your app is moved into the production database you can no longer change the data structure.

The supported record types are NSString, NSNumber, NSData, NSDate, CLLocation, CKReference, CKAsset; a record can also be an array instead of a single record type (Listing 4-1).

Listing 4-1. Different Ways of Getting and Setting CKRecord Values
let park = CKRecord(recordType: "Park")
// Setting values
park.setObject("Lafarge Lake", forKey: "name")
park["location"] = "Vancouver, BC"


// Accessing values
var name = park.objectForKey("name")
var location = park["location"]

Record Zones

Records are grouped within record zones . There can be multiple record zones. Every container has a default record zone.

Record Identifiers

CKRecordID contains a recordName and CKRecordZoneID. If you don’t provide a CKRecordZoneID the default record zone will be used. If you don’t provide a recordName a default one will be created for you.

References

CKReference allows you to relate CKRecords with each other. When creating references you should use back references; for example, park images should have a reference to their parent, instead of the park having a list of images. References can be set to cascade delete, so if a park is deleted all related images are also deleted at the same time.

Assets

CKAssets are used to store large files such as images or videos and are stored as bulk storage. CloudKit takes care of efficiently uploading and downloading assets for you. Assets are owned by CKRecords and are transferred as files on disk. See Listing 4-2.

Listing 4-2. Add an Asset to a CKRecord
let photoURL = NSURL(fileURLWithPath: "...")
let parkThumbnail = CKAsset(fileURL: photoURL)
park["thumbnail"] = parkThumbnail

Convenience API

You can save a record using saveRecord with the completionHandler call on either the public or private databases. This is an asynchronous method call, so make sure to handle errors, as this can be the difference between a functional and non-functional app. See Listing 4-3.

Listing 4-3. Code for Saving a Record
// Create a record ID or allow CloudKit to create a
// random one for you.
let recordID = CKRecordID(recordName: "vanLostParkID")
// Create a new record using the RecordID if created
let park = CKRecord(recordType: "Park", recordID: recordID)
// Choose either the public or private database where you
// want to save the record
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
// Use the convience API saveRecord with completionHandler
publicDatabase.saveRecord(park) { (savedPark: CKRecord?, error: NSError?) -> Void in
   // Handle any errors when error != nil
}

You can fetch a specific record using a predetermined CKRecordID and the fetchRecordWithID and completionHandler convenience API (Listing 4-4).

Listing 4-4. How to Fetch a Record
// Fetching a record from CloudKit
// Use a predetermined CKRecordID
let recordID = CKRecordID(recordName: "vanLostParkID")
// Choose either the public or private database where
// you want to save the record
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
// Use the convenience API fetchRecordWithID
// and completionHandler
publicDatabase.fetchRecordWithID(recordID) { (park: CKRecord?, error: NSError?) -> Void in
   // Handle any errors when error != nil
}

As shown in Listing 4-5, you can query a record, modify it, and then save it back to the server.

Listing 4-5. How to Query a Record, Modify It, and Save It Back to CloudKit
// Fetching a record from CloudKit
// Use a predetermined CKRecordID
let recordID = CKRecordID(recordName: "vanLostParkID")
// Choose either the public or private database
// where you want to save the record
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
// Use the convenience API fetchRecordWithID
// and completionHandler
publicDatabase.fetchRecordWithID(recordID) { (fetchedPark: CKRecord?, error: NSError?) -> Void in
   // Handle any errors when error != nil
   if error != nil {} else {
     // Modify the fetched park
     fetchedPark!["name"] = "Updated Park Name"
     // Save the modified park back to CloudKit
     publicDatabase.saveRecord(fetchedPark!, completionHandler: { (savedPark: CKRecord?, error: NSError?) -> Void in
       // Handle any errors when error != nil
     })
   }
}

Queries

CKQuery combines a RecordType, an NSPredicate, and NSSortDescriptors (optional) to give users a focused chunk of data to work with. CloudKit supports only subsets of the NSPredictate features. Queries are polls from the database; they should not be used for querying data that returns the same result sets over and over again. See Listing 4-6.

Listing 4-6. Querying CloudKit Using a Predicate
let predicate = NSPredicate(format: "name = %@", "Updated Park Name")

let query = CKQuery(recordType: "Park", predicate: predicate)
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase


publicDatabase.performQuery(query, inZoneWithID: nil) { (results: [CKRecord]?, error: NSError?) -> Void in
   // Handle errors
   if error == nil {
      for record in results! {
          print("(record)")
      }
   }
}

Subscriptions

CKSubcription combines a RecordType, an NSPredicate, and a Push. This allows the server to push any new changes to any devices listening instead of requiring the device to poll for changes (Listing 4-7).

Listing 4-7. Setting Up a Subscription
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let predicate = NSPredicate(format: "name = %@", "Updated Park Name")
// Send a push notification whenever the record(s)
// found from the predicate change
let subscription = CKSubscription(recordType: "Park", predicate: predicate, options: CKSubscriptionOptions.FiresOnRecordUpdate)
// Tell the application how to handle the notification
let notificationInfo = CKNotificationInfo()
notificationInfo.alertLocalizationKey = "LOCAL_NOTIFICATION_KEY"
notificationInfo.soundName = "Park.aiff"
notificationInfo.shouldBadge = true
subscription.notificationInfo = notificationInfo


// Save the subscription to the server
publicDatabase.saveSubscription(subscription) { (subscription: CKSubscription?, error: NSError?) -> Void in
   // Handle errors when error != nil
}


// Update application:didReceiveRemoteNotification
// to handle CloudKit notifcations
func application(application: NSApplication, didReceiveRemoteNotification userInfo: [String : AnyObject]) {
   let cloudKitNotification = CKNotification(fromRemoteNotificationDictionary: userInfo as! [String: NSObject])
   let alertBody = cloudKitNotification.alertBody


   if cloudKitNotification.notificationType == CKNotificationType.Query {
     let queryNotification: CKQueryNotification = cloudKitNotification as! CKQueryNotification
     let recordID = queryNotification.recordID
   }
}

CloudKit User Accounts

CloudKit user accounts provide a way to identify the user with metadata about the user. Any client talking to the same container will be returned the same ID for the logged-in user, regardless of whether the client is OS X, iOS, or web using CloudKitJS (Listing 4-8). You can also leverage this identity to uniquely interact with your own servers without requiring the user to log in.

Listing 4-8. Getting the Current Logged-in User’s Identifier
// This gets the current logged-in user's identifier CKContainer.defaultContainer().fetchUserRecordIDWithCompletionHandler { (userRecordID: CKRecordID?, error: NSError?) -> Void in
   // Handle errors when error != nil
}

Metadata allows us to set key-value pairs on a user record. User records in the public database are world readable (Listing 4-9). You cannot query user records.

Listing 4-9. Getting Metadata about a User
let defaultContainer = CKContainer.defaultContainer()
let publicDatabase = defaultContainer.publicCloudDatabase
defaultContainer.fetchUserRecordIDWithCompletionHandler { (userRecordID: CKRecordID?, error: NSError?) -> Void in
   // Handle errors when error != nil
   if error != nil {} else {
      // Get the user record from CloudKit using the
      // recordID for the logged-in user
      publicDatabase.fetchRecordWithID(userRecordID!, completionHandler: { (userRecord: CKRecord?, error: NSError?) -> Void in
         // Handle errors
         if error != nil {} else {
           // User records are like any other records; you
           // can add key-values and resave them to the
           // CloudKit database.
           // Assuming we have a displayname
           var displayName = userRecord!["displayName"]
           print("(displayName)")
         }
       })
    }
}

For privacy reasons no personal identifying information is provided by default. You can request that information via CloudKit, and the user has to either accept or decline providing personal information.

User discovery allows you to gather information from the user if they have opted in to being discoverable. You can also leverage the address book to find any of your contacts that are also using the server, but they will have to enable discovery first (Listing 4-10). Leveraging the address book through CloudKit does not require the user to authorize you to access their address book; you are just accessing your own.

Listing 4-10. Using the Address Book to Find Information about Other Users
let defaultContainer = CKContainer.defaultContainer()
        defaultContainer.discoverAllContactUserInfosWithCompletionHandler { (userInfos: [CKDiscoveredUserInfo]?, error: NSError?) -> Void in
    // Handle errors
    if error != nil {} else {
        for userInfo in userInfos! {
           // familyName will only show if the user
           // has oped in to showing it.
           print("(userInfo.userRecordID) (userInfo.displayContact?.familyName)")
        }
   }
}

CloudKit Dashboard

When you log in to https://icloud.developer.apple.com/dashboard you will be presented with the screen shown in Figure 4-1. Let’s quickly go over what the Dashboard contains.

A385012_1_En_4_Fig1_HTML.jpg
Figure 4-1. CloudKit Dashboard

First, in the top left-hand corner you will find a drop-down. This drop-down contains a list of all the app containers that you have enabled for CloudKit. When you select the one you want to work with, the page is updated to show the data in the selected container.

Schema Record Types

The user’s type represents a CloudKit-generated record type that cannot be deleted. You can, however, add additional fields to this record type. The User record type, as the name suggests, is used to store information about each user who has used your app.

When you select a record type from the Record Types column, the detail pane will update to show all fields related to the selected type. This is also where you can set metadata indexes and security settings for a given record type.

You can create a new type by clicking the + symbol at the top left-hand corner of the detail pane (Figure 4-2).

A385012_1_En_4_Fig2_HTML.jpg
Figure 4-2. Creating a new record type

You are then asked to name the new type. It is important to enter a name before you save, as you cannot change it once the type has been saved. Notice that the Created and Modified fields are automatically created for each record type; you cannot delete these fields. There is also a field automatically added, which you should change before saving. To add additional fields simply click the “Add Field...” link. You can either add all the fields when you are first creating the type, update them later, or when in development mode allow your app to dynamically add new ones through code. As you can see from Figure 4-3, there are several options for field type, which can either be a single entry or a list.

A385012_1_En_4_Fig3_HTML.jpg
Figure 4-3. List of the available field types

Finally, you can set indexes for individual fields, which is important if you find your app is always querying a specific field of a record type.

If after you save you realize you do not want the new record type anymore, simply select the type from the list and click on the trashcan icon in the details pane to delete it. For the Users type, the trashcan is disabled because you are unable to delete this type.

If you have added a field you no longer wish to have, select the record type and hover the cursor over the field you wish to delete. In the cost column you will see an X; clicking on this deletes the field.

Security Roles

The Security Roles section under Schema allows you to control who can update, create, and view different record types. This is a two-step process. First, you must create a security role. Second, you must add that role to the users to whom you wish to give those permissions.

By default there are no security roles created. To create a new one, click on the + sign in the detail pane. You are asked to name the role. Do so and click Save. Now you can assign permissions to the record types. You can add as many record type permissions as you want per security role, and you can give each security role permission to create, read, or write to a given record type (Figure 4-4).

A385012_1_En_4_Fig4_HTML.jpg
Figure 4-4. New ParkManager security role

We will cover how to assign these roles to a user once when we go over public data.

Subscription Types

This section lists any subscription types that have been created. You can only view them in the Dashboard and cannot create them. They must be created in your app itself.

Public Data User Records

If you haven’t set any metadata indexes for a user record, you will see the following issue: “Records of this type cannot be shown because there is no Query Index of Record ID field.” Below the message, click the link that says “Add Record ID Query Index.” After that, any users that have used your app will show up. If the user has not shared their name, the record will display as No Name.

If you want to assign a user to a security role you have created, select the user in the list or search for the user by clicking the magnifying glass icon. Next, in the detail pane, select “Roles” from the drop-down under Security and check any roles you want this user to have (Figure 4-5).

A385012_1_En_4_Fig5_HTML.jpg
Figure 4-5. How to assign a security role to a user

Default Zone

The Default Zone shows all data that is in the public database. You need to use the drop-down in the second pane to select which record type you want to see data for. Once the data type is selected, the data for the record type will be displayed. When you select a row, the detail pane will show all the information for that record that can be edited or deleted. You can also create new rows for a record type by using the + icon in the detail pane. Finally, you can use the Search and Filter functionalities to narrow down the search results.

Usage

The Usage section shows you how many resources your app is currently using and how close it is to the limit. It also shows a predictive forecast of how much your app might use the specified resources over time. Apple gives a generous number of free resources that increases as the number of app users increases. It is important to monitor this section to ensure you don’t have any major bugs that are using resources, especially as regards requests per second.

Private Data Default Zone

If you use your app with the same iCloud account as your CloudKit developer account uses, you will be able to see any private records for that user account here. Otherwise, if you use a second iCloud account, you will see an empty result set for all of the record types.

Admin Team

This section lists all the members on your development team . You can enabled or disable privileges for each of them in regards to managing the team, edit development, and edit production. You can also view when a member last logged in, their name, and their Apple ID.

API Access

In this section you can enable an access token that can be used with the CloudKitJS SDK to enable you to build web applications that interact with CloudKit. This is out of the scope of this book, and for those interested you should view the Apple WWDC videos related to the JS SDK.

Deployment

The Deployment section allows you to reset the development environment, which will clear all data and delete all record types. This is also the place where you deploy your metadata to production. This will only move your schemas, not any of your development data. It is important to remember that once you deploy to production you will no longer be able to delete field types, so always make sure you have fully tested your app in development before moving it to production.

Conclusion

This chapter has provided an overview of CloudKit, along with the CloudKit Dashboard. We mainly focused on the CloudKit convenience API; however, in later chapters we will be using the NSOperations CloudKit API.

For additional information on CloudKit, it is recommended that you watch all the WWDC videos ( https://developer.apple.com/videos/wwdc2015/ ) related to CloudKit before moving on to the next chapter.

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

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