Slide 1

Slide 1 text

CloudKit Just another iCloud API? Michael Ochs

Slide 2

Slide 2 text

What is CloudKit?

Slide 3

Slide 3 text

What is CloudKit? “The CloudKit framework provides interfaces for moving data between your app and your iCloud containers” - CloudKit Framework Reference

Slide 4

Slide 4 text

What is CloudKit? “CloudKit is not a replacement for your app’s existing data objects. Instead, CloudKit provides complementary services for managing the transfer of data to and from iCloud servers.” - CloudKit Framework Reference

Slide 5

Slide 5 text

What is CloudKit? • it is a transfer api to move data to and from the cloud • it behaves like a remote database in many parts • but: It is not your app’s database

Slide 6

Slide 6 text

Structure

Slide 7

Slide 7 text

Structure • local api • remote database • remote dashboard • records • references • containers • queries • operations • zones • subscriptions

Slide 8

Slide 8 text

Structure Data Container Public Database Private Database Private Database Private Database Private Database Private Database Private Database Private Database Private Database Private Database Bulk Storage Zone Record Record Record Asset Asset Asset

Slide 9

Slide 9 text

Structure Convenience API CKContainer CKDatabase CKRecord

Slide 10

Slide 10 text

Structure Convenience API CKContainer CKDatabase CKRecord CKQuery

Slide 11

Slide 11 text

Structure Convenience API CKContainer CKDatabase CKSubscription

Slide 12

Slide 12 text

Structure Not so convenient API CKContainer CKDatabase CKRecord CKModifyRecordsOperation CKRecord CKQueryOperation CKQuery CKRecord CKModifySubscriptionsOperation CKSubscription

Slide 13

Slide 13 text

Structure Inconvenient API CKModifyRecordsOperation CKQueryOperation CKModifySubscriptionsOperation CKModifyRecordZonesOperation CKFetchSubscriptionsOperation CKFetchRecordZonesOperation CKFetchRecordsOperation CKFetchRecordChangesOperation CKDiscoverAllContactsOperation CKDiscoverUserInfosOperation CKFetchNotificationChangesOperation CKMarkNotificationsReadOperation CKModifyBadgeOperation

Slide 14

Slide 14 text

CKModifyRecordsOperation CKQueryOperation CKModifySubscriptionsOperation CKModifyRecordZonesOperation CKFetchSubscriptionsOperation CKFetchRecordZonesOperation CKFetchRecordsOperation CKFetchRecordChangesOperation CKDiscoverAllContactsOperation CKDiscoverUserInfosOperation CKFetchNotificationChangesOperation CKMarkNotificationsReadOperation CKModifyBadgeOperation Structure InternalError PartialFailure NetworkUnavailable NetworkFailure BadContainer ServiceUnavailable RequestRateLimited MissingEntitlement NotAuthenticated PermissionFailure UnknownItem InvalidArguments ResultsTruncated ServerRecordChanged ServerRejectedRequest AssetFileNotFound AssetFileModified IncompatibleVersion ConstraintViolation OperationCancelled ChangeTokenExpired BatchRequestFailed ZoneBusy BadDatabase QuotaExceeded ZoneNotFound

Slide 15

Slide 15 text

CKModifyRecordsOperation CKQueryOperation CKModifySubscriptionsOperation CKModifyRecordZonesOperation CKFetchSubscriptionsOperation CKFetchRecordZonesOperation CKFetchRecordsOperation CKFetchRecordChangesOperation CKDiscoverAllContactsOperation CKDiscoverUserInfosOperation CKFetchNotificationChangesOperation CKMarkNotificationsReadOperation CKModifyBadgeOperation Structure Do not start with CloudKit in your productive application!

Slide 16

Slide 16 text

Structure • This api has nothing to do with convenience • …but this api is great • It gives you a lot of responsibility • …but also a lot of power and flexibility

Slide 17

Slide 17 text

Dashboard

Slide 18

Slide 18 text

Dashboard • Web based administration • View, create, edit, and remove records • Edit, and remove record layouts • Edit access groups / privileges

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Demo

Slide 22

Slide 22 text

CKDatabase

Slide 23

Slide 23 text

CKDatabase • Public database • readable by everyone • writable by every iCloud user • Private database • readable and writable by the current iCloud user

Slide 24

Slide 24 text

CKDatabase CKContainer *container = [CKContainer defaultContainer];
 CKDatabase *database = [container publicCloudDatabase]; Public Database

Slide 25

Slide 25 text

CKDatabase CKContainer *container = [CKContainer defaultContainer];
 CKDatabase *database = [container publicCloudDatabase]; Public Database

Slide 26

Slide 26 text

CKDatabase CKContainer *container = [CKContainer defaultContainer];
 CKDatabase *database = [container publicCloudDatabase]; Public Database

Slide 27

Slide 27 text

CKDatabase CKContainer *container = [CKContainer defaultContainer];
 CKDatabase *database = [container privateCloudDatabase]; Private Database

Slide 28

Slide 28 text

CKDatabase CKContainer *container = [CKContainer defaultContainer];
 CKDatabase *database = [container privateCloudDatabase]; Private Database

Slide 29

Slide 29 text

CKDatabase CKRecordID *recordID = …;
 [database deleteRecordWithID:recordID
 completionHandler:^(CKRecordID *recordID, NSError *error) {
 if (error) {
 dispatch_async(dispatch_get_main_queue(), ^{
 [self presentError:error
 completionHandler:^(BOOL didRecover){
 // TODO: handle error
 }];
 });
 return;
 }
 
 // TODO: handle success
 }];

Slide 30

Slide 30 text

CKDatabase CKRecordID *recordID = …;
 [database deleteRecordWithID:recordID
 completionHandler:^(CKRecordID *recordID, NSError *error) {
 if (error) {
 dispatch_async(dispatch_get_main_queue(), ^{
 [self presentError:error
 completionHandler:^(BOOL didRecover){
 // TODO: handle error
 }];
 });
 return;
 }
 
 // TODO: handle success
 }];

Slide 31

Slide 31 text

CKDatabase CKRecordID *recordID = …;
 [database deleteRecordWithID:recordID
 completionHandler:^(CKRecordID *recordID, NSError *error) {
 if (error) {
 dispatch_async(dispatch_get_main_queue(), ^{
 [self presentError:error
 completionHandler:^(BOOL didRecover){
 // TODO: handle error
 }];
 });
 return;
 }
 
 // TODO: handle success
 }];

Slide 32

Slide 32 text

CKRecord

Slide 33

Slide 33 text

CKRecord • Data object • Dictionary like api • On the fly model generation

Slide 34

Slide 34 text

CKRecord Each record has a… • …record type • …record id • …creation date / user record id • …modification date / user record id

Slide 35

Slide 35 text

CKRecord Class record type record id creation date / user record id modification date / user record id Type NSString* CKRecordID* NSDate* / CKRecordID* NSDate* / CKRecordID*

Slide 36

Slide 36 text

CKRecord CloudKit record type record id creation date / user record id modification date / user record id CoreData entity name object id n/a n/a

Slide 37

Slide 37 text

CKRecord CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Todo"];
 record[@"title"] = @"Get christmas presents";
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store record id to your local model
 }];

Slide 38

Slide 38 text

CKRecord CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Todo"];
 record[@"title"] = @"Get christmas presents";
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store record id to your local model
 }];

Slide 39

Slide 39 text

CKRecord CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Todo"];
 record[@"title"] = @"Get christmas presents";
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store record id to your local model
 }];

Slide 40

Slide 40 text

CKRecord CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Todo"];
 record[@"title"] = @"Get christmas presents";
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store record id to your local model
 }];

Slide 41

Slide 41 text

CKRecord CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Todo"];
 record[@"title"] = @"Get christmas presents";
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store record id to your local model
 }];

Slide 42

Slide 42 text

Container Public Database Zone CKRecord Todo creationDate modificationDate title

Slide 43

Slide 43 text

CKRecord CKRecordID *recordID = …; // get record id from your model
 [database fetchRecordWithID:recordID
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 record[@"done"] = @YES;
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 // TODO: check & handle error
 }];
 }];

Slide 44

Slide 44 text

CKRecord CKRecordID *recordID = …; // get record id from your model
 [database fetchRecordWithID:recordID
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 record[@"done"] = @YES;
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 // TODO: check & handle error
 }];
 }];

Slide 45

Slide 45 text

CKRecord CKRecordID *recordID = …; // get record id from your model
 [database fetchRecordWithID:recordID
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 record[@"done"] = @YES;
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 // TODO: check & handle error
 }];
 }];

Slide 46

Slide 46 text

CKRecord CKRecordID *recordID = …; // get record id from your model
 [database fetchRecordWithID:recordID
 completionHandler:^(CKRecord *record, NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 record[@"done"] = @YES;
 
 [database saveRecord:record
 completionHandler:^(CKRecord *record, NSError *error) {
 // TODO: check & handle error
 }];
 }];

Slide 47

Slide 47 text

Container Public Database Zone CKRecord Todo creationDate modificationDate title Todo creationDate modificationDate title done

Slide 48

Slide 48 text

CKSubscription

Slide 49

Slide 49 text

CKSubscription • Subscribe to push notifications • Bound to a record type & predicate • on create / on update / on delete • silent / badge / alert / sound

Slide 50

Slide 50 text

CKSubscription • Configure push notifications • Register for push notifications • Subscribe to cloud kit

Slide 51

Slide 51 text

CKSubscription - (BOOL)application:(UIApplication *)application
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 [application registerForRemoteNotifications];
 return YES;
 }
 
 - (void)application:(UIApplication *)application
 didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
 // trigger subscription
 }

Slide 52

Slide 52 text

CKSubscription - (BOOL)application:(UIApplication *)application
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 [application registerForRemoteNotifications];
 return YES;
 }
 
 - (void)application:(UIApplication *)application
 didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
 // trigger subscription
 }

Slide 53

Slide 53 text

CKSubscription - (BOOL)application:(UIApplication *)application
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 [application registerForRemoteNotifications];
 return YES;
 }
 
 - (void)application:(UIApplication *)application
 didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
 // trigger subscription
 }

Slide 54

Slide 54 text

CKSubscription NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
 
 CKSubscriptionOptions options = (
 CKSubscriptionOptionsFiresOnRecordUpdate |
 CKSubscriptionOptionsFiresOnRecordDeletion |
 CKSubscriptionOptionsFiresOnRecordCreation);
 
 CKSubscription *subscription = [[CKSubscription alloc]
 initWithRecordType:@"Todo"
 predicate:predicate
 options:options];

Slide 55

Slide 55 text

CKSubscription NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
 
 CKSubscriptionOptions options = (
 CKSubscriptionOptionsFiresOnRecordUpdate |
 CKSubscriptionOptionsFiresOnRecordDeletion |
 CKSubscriptionOptionsFiresOnRecordCreation);
 
 CKSubscription *subscription = [[CKSubscription alloc]
 initWithRecordType:@"Todo"
 predicate:predicate
 options:options];

Slide 56

Slide 56 text

CKSubscription NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
 
 CKSubscriptionOptions options = (
 CKSubscriptionOptionsFiresOnRecordUpdate |
 CKSubscriptionOptionsFiresOnRecordDeletion |
 CKSubscriptionOptionsFiresOnRecordCreation);
 
 CKSubscription *subscription = [[CKSubscription alloc]
 initWithRecordType:@"Todo"
 predicate:predicate
 options:options];

Slide 57

Slide 57 text

CKSubscription NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
 
 CKSubscriptionOptions options = (
 CKSubscriptionOptionsFiresOnRecordUpdate |
 CKSubscriptionOptionsFiresOnRecordDeletion |
 CKSubscriptionOptionsFiresOnRecordCreation);
 
 CKSubscription *subscription = [[CKSubscription alloc]
 initWithRecordType:@"Todo"
 predicate:predicate
 options:options];

Slide 58

Slide 58 text

CKSubscription CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
 notificationInfo.shouldSendContentAvailable = YES;
 
 subscription.notificationInfo = notificationInfo;
 
 [database saveSubscription:subscription
 completionHandler:^(CKSubscription *subscription,
 NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store subscription id
 }];

Slide 59

Slide 59 text

CKSubscription CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
 notificationInfo.shouldSendContentAvailable = YES;
 
 subscription.notificationInfo = notificationInfo;
 
 [database saveSubscription:subscription
 completionHandler:^(CKSubscription *subscription,
 NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store subscription id
 }];

Slide 60

Slide 60 text

CKSubscription CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
 notificationInfo.shouldSendContentAvailable = YES;
 
 subscription.notificationInfo = notificationInfo;
 
 [database saveSubscription:subscription
 completionHandler:^(CKSubscription *subscription,
 NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store subscription id
 }];

Slide 61

Slide 61 text

CKSubscription CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
 notificationInfo.shouldSendContentAvailable = YES;
 
 subscription.notificationInfo = notificationInfo;
 
 [database saveSubscription:subscription
 completionHandler:^(CKSubscription *subscription,
 NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store subscription id
 }];

Slide 62

Slide 62 text

CKSubscription CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
 notificationInfo.shouldSendContentAvailable = YES;
 
 subscription.notificationInfo = notificationInfo;
 
 [database saveSubscription:subscription
 completionHandler:^(CKSubscription *subscription,
 NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store subscription id
 }];

Slide 63

Slide 63 text

CKSubscription CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
 notificationInfo.shouldSendContentAvailable = YES;
 
 subscription.notificationInfo = notificationInfo;
 
 [database saveSubscription:subscription
 completionHandler:^(CKSubscription *subscription,
 NSError *error) {
 if (error) {
 // TODO: handle error
 return;
 }
 // TODO: store subscription id
 }];

Slide 64

Slide 64 text

CKSubscription - (void)application:(UIApplication *)application
 didReceiveRemoteNotification:(NSDictionary *)userInfo
 fetchCompletionHandler:
 (void(^)(UIBackgroundFetchResult))completionHandler {
 
 // TODO: fetch updates and handle them
 completionHandler(UIBackgroundFetchResultNewData);
 
 }

Slide 65

Slide 65 text

CKSubscription - (void)application:(UIApplication *)application
 didReceiveRemoteNotification:(NSDictionary *)userInfo
 fetchCompletionHandler:
 (void(^)(UIBackgroundFetchResult))completionHandler {
 
 // TODO: fetch updates and handle them
 completionHandler(UIBackgroundFetchResultNewData);
 
 }

Slide 66

Slide 66 text

CKSubscription - (void)application:(UIApplication *)application
 didReceiveRemoteNotification:(NSDictionary *)userInfo
 fetchCompletionHandler:
 (void(^)(UIBackgroundFetchResult))completionHandler {
 
 // TODO: fetch updates and handle them
 completionHandler(UIBackgroundFetchResultNewData);
 
 }

Slide 67

Slide 67 text

CKSubscription • mark notifications as read • fetch missed notifications • handle badges on all devices

Slide 68

Slide 68 text

// TODO: handle error

Slide 69

Slide 69 text

// TODO: handle error • You need to handle them • Otherwise your data models will become inconsistent • They might occur often

Slide 70

Slide 70 text

// TODO: handle error • Handle errors in a central place if possible • HRSCustomErrorHandling might help you

Slide 71

Slide 71 text

// TODO: handle error CloudKit Transaction cache Data model

Slide 72

Slide 72 text

// TODO: handle error CloudKit Transaction cache Data model

Slide 73

Slide 73 text

Problems

Slide 74

Slide 74 text

Problems • convenient API can not handle complexity of CloudKit • lack of documentation • strange behavior • iOS simulator is not working • privileges handling is lacking features

Slide 75

Slide 75 text

Next steps

Slide 76

Slide 76 text

Next steps • experiment with the convenient api • check if CloudKit is the right iCloud api for your task • move to the operation based api • get your models together

Slide 77

Slide 77 text

Feedback / Questions @_mochs ios-coding.com

Slide 78

Slide 78 text

Thank you