Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CloudKit - Just another iCloud API?

Michael Ochs
December 15, 2014

CloudKit - Just another iCloud API?

This is a talk about the structure and my first steps with CloudKit. It shows how you could start learning CloudKit and lists a number of pitfalls I ran into, when I started my first project.

Michael Ochs

December 15, 2014
Tweet

More Decks by Michael Ochs

Other Decks in Programming

Transcript

  1. CloudKit
    Just another iCloud API?
    Michael Ochs

    View Slide

  2. What is CloudKit?

    View Slide

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

    View Slide

  4. 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

    View Slide

  5. 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

    View Slide

  6. Structure

    View Slide

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

    View Slide

  8. 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

    View Slide

  9. Structure
    Convenience API
    CKContainer CKDatabase
    CKRecord

    View Slide

  10. Structure
    Convenience API
    CKContainer CKDatabase
    CKRecord
    CKQuery

    View Slide

  11. Structure
    Convenience API
    CKContainer CKDatabase CKSubscription

    View Slide

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

    View Slide

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

    View Slide

  14. 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

    View Slide

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

    View Slide

  16. 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

    View Slide

  17. Dashboard

    View Slide

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

    View Slide

  19. View Slide

  20. View Slide

  21. Demo

    View Slide

  22. CKDatabase

    View Slide

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

    View Slide

  24. CKDatabase
    CKContainer *container = [CKContainer defaultContainer];

    CKDatabase *database = [container publicCloudDatabase];
    Public Database

    View Slide

  25. CKDatabase
    CKContainer *container = [CKContainer defaultContainer];

    CKDatabase *database = [container publicCloudDatabase];
    Public Database

    View Slide

  26. CKDatabase
    CKContainer *container = [CKContainer defaultContainer];

    CKDatabase *database = [container publicCloudDatabase];
    Public Database

    View Slide

  27. CKDatabase
    CKContainer *container = [CKContainer defaultContainer];

    CKDatabase *database = [container privateCloudDatabase];
    Private Database

    View Slide

  28. CKDatabase
    CKContainer *container = [CKContainer defaultContainer];

    CKDatabase *database = [container privateCloudDatabase];
    Private Database

    View Slide

  29. 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

    }];

    View Slide

  30. 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

    }];

    View Slide

  31. 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

    }];

    View Slide

  32. CKRecord

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  36. 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

    View Slide

  37. 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

    }];

    View Slide

  38. 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

    }];

    View Slide

  39. 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

    }];

    View Slide

  40. 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

    }];

    View Slide

  41. 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

    }];

    View Slide

  42. Container
    Public Database
    Zone
    CKRecord
    Todo
    creationDate
    modificationDate
    title

    View Slide

  43. 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

    }];

    }];

    View Slide

  44. 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

    }];

    }];

    View Slide

  45. 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

    }];

    }];

    View Slide

  46. 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

    }];

    }];

    View Slide

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

    View Slide

  48. CKSubscription

    View Slide

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

    View Slide

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

    View Slide

  51. CKSubscription
    - (BOOL)application:(UIApplication *)application

    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [application registerForRemoteNotifications];

    return YES;

    }


    - (void)application:(UIApplication *)application

    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

    // trigger subscription

    }

    View Slide

  52. CKSubscription
    - (BOOL)application:(UIApplication *)application

    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [application registerForRemoteNotifications];

    return YES;

    }


    - (void)application:(UIApplication *)application

    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

    // trigger subscription

    }

    View Slide

  53. CKSubscription
    - (BOOL)application:(UIApplication *)application

    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [application registerForRemoteNotifications];

    return YES;

    }


    - (void)application:(UIApplication *)application

    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

    // trigger subscription

    }

    View Slide

  54. CKSubscription
    NSPredicate *predicate = [NSPredicate predicateWithValue:YES];


    CKSubscriptionOptions options = (

    CKSubscriptionOptionsFiresOnRecordUpdate |

    CKSubscriptionOptionsFiresOnRecordDeletion |

    CKSubscriptionOptionsFiresOnRecordCreation);


    CKSubscription *subscription = [[CKSubscription alloc]

    initWithRecordType:@"Todo"

    predicate:predicate

    options:options];

    View Slide

  55. CKSubscription
    NSPredicate *predicate = [NSPredicate predicateWithValue:YES];


    CKSubscriptionOptions options = (

    CKSubscriptionOptionsFiresOnRecordUpdate |

    CKSubscriptionOptionsFiresOnRecordDeletion |

    CKSubscriptionOptionsFiresOnRecordCreation);


    CKSubscription *subscription = [[CKSubscription alloc]

    initWithRecordType:@"Todo"

    predicate:predicate

    options:options];

    View Slide

  56. CKSubscription
    NSPredicate *predicate = [NSPredicate predicateWithValue:YES];


    CKSubscriptionOptions options = (

    CKSubscriptionOptionsFiresOnRecordUpdate |

    CKSubscriptionOptionsFiresOnRecordDeletion |

    CKSubscriptionOptionsFiresOnRecordCreation);


    CKSubscription *subscription = [[CKSubscription alloc]

    initWithRecordType:@"Todo"

    predicate:predicate

    options:options];

    View Slide

  57. CKSubscription
    NSPredicate *predicate = [NSPredicate predicateWithValue:YES];


    CKSubscriptionOptions options = (

    CKSubscriptionOptionsFiresOnRecordUpdate |

    CKSubscriptionOptionsFiresOnRecordDeletion |

    CKSubscriptionOptionsFiresOnRecordCreation);


    CKSubscription *subscription = [[CKSubscription alloc]

    initWithRecordType:@"Todo"

    predicate:predicate

    options:options];

    View Slide

  58. 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

    }];

    View Slide

  59. 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

    }];

    View Slide

  60. 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

    }];

    View Slide

  61. 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

    }];

    View Slide

  62. 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

    }];

    View Slide

  63. 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

    }];

    View Slide

  64. CKSubscription
    - (void)application:(UIApplication *)application

    didReceiveRemoteNotification:(NSDictionary *)userInfo

    fetchCompletionHandler:

    (void(^)(UIBackgroundFetchResult))completionHandler {


    // TODO: fetch updates and handle them

    completionHandler(UIBackgroundFetchResultNewData);


    }

    View Slide

  65. CKSubscription
    - (void)application:(UIApplication *)application

    didReceiveRemoteNotification:(NSDictionary *)userInfo

    fetchCompletionHandler:

    (void(^)(UIBackgroundFetchResult))completionHandler {


    // TODO: fetch updates and handle them

    completionHandler(UIBackgroundFetchResultNewData);


    }

    View Slide

  66. CKSubscription
    - (void)application:(UIApplication *)application

    didReceiveRemoteNotification:(NSDictionary *)userInfo

    fetchCompletionHandler:

    (void(^)(UIBackgroundFetchResult))completionHandler {


    // TODO: fetch updates and handle them

    completionHandler(UIBackgroundFetchResultNewData);


    }

    View Slide

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

    View Slide

  68. // TODO: handle error

    View Slide

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

    View Slide

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

    View Slide

  71. // TODO: handle error
    CloudKit
    Transaction cache
    Data model

    View Slide

  72. // TODO: handle error
    CloudKit
    Transaction cache
    Data model

    View Slide

  73. Problems

    View Slide

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

    View Slide

  75. Next steps

    View Slide

  76. 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

    View Slide

  77. Feedback / Questions
    @_mochs
    ios-coding.com

    View Slide

  78. Thank you

    View Slide