Migrating from Version 1.x to Version 3.x

Version 3 of the Kinvey libraries adds a number of new capabilities, and incorporates feedback from our developers into a powerful, improved API. We've simplified our libraries to enable common use cases such as syncing, offline usage, user management and querying with minimal code for the app developer.

We recommend that all Kinvey apps use the latest version of the library. This guide is intended to help developers who wish to migrate to v3 from an earlier Kinvey library version.

What's New in Version 3

Version 3 is a major overhaul of the Kinvey mobile libraries that improves on the previous versions and adds several new features. Here are some highlights of the new capabilities of v3:

Data Store Types and Sync

Version 3 simplifies the DataStore with built-in caching and syncing capabilities. We have designed our data stores in a way that makes it easy for developers to support different caching and offline use cases. All you need to do is specify a type when a data store is created. The library takes care of configuring read and write policies, creating and managing a cache, keeping track of offline write operations and optimizing data transfers between the device and the backend. Syncing your backend in Version 3 is much easier with the new pull(), push() and sync() APIs on the DataStores.

For more, check out our DataStore guide.

Querying

Queries can be expressed using the native syntax you are familiar with - for example, you can write queries as NSPredicates on iOS or Lync on Xamarin. The library uses the native expressions to query the cache locally, and seamlessly translates the query when it is run against the backend.

For more, check out the section on querying in our DataStore guide.

Delta Set Caching

In Version 3, we designed a mechanism to reduce the amount of data transferred between the app and the backend on a data fetch. We call this mechanism "delta set caching", and we recommend that developers enable it to gain significant performance benefits in data synchronization.

For more, check out our caching and offline guide.

Resumable File Uploads

Version 3 supports resumable file uploads with Google Cloud Storage. The library will try to upload as much of a file as possible and handle interruptions seamlessly by resuming where it left off.

For more, check out our file guide.

Before you start

The following table lays out our supported languages. In order to use the new version of the library, your project must be written in Swift:

Language / VersionKinvey SDK VersionLatest Version
Swift 33.3Download Version 3.3.9
Swift 2.33.2Download Version 3.2.6
Swift 2.23.1 (deprecated)Download Version 3.1.0
Objective-C1.x (deprecated)Download Version 1.41.4

Swift 2.3 Deprecation

Xcode 8.2 is now out. Apple has announced that Xcode 8.2 will be the last release to support Swift 2.3. Read Xcode 8.2 Release Notes for more details. Please migrate your projects from Swift 2.3 to Swift 3 as soon as possible, by selecting Edit > Convert > To Current Swift Syntax when you open your project.

The identifier for the library has changed from KinveyKit to Kinvey. If you are using CocoaPods to manage dependencies, you will need to rename the Kinvey dependency in your Podfile.

Objective-C Apps

The v3 APIs are written in Swift and use new Swift language constructs. As a result, all code calling into the v3 SDK needs to be written in Swift.

Why v3 requires Swift

Swift introduces several new features and aims to improve on Obj-C by addressing concerns such as type safety, readability and speed. Swift adoption as the language of choice for iOS developers has gained rapidly, led by Apple's focus on Swift. When we built v3, we decided to move in that direction as well. All of our new code and APIs are written in Swift. Adopting Swift also allowed us to use the best tools and frameworks for key components in the SDK. For example, we integrated frameworks such as ObjectMapper, PromiseKit and Realm, some of which are only available in Swift.

Swift supports interoperability with Obj-C; however, several new constructs introduced in Swift are not portable to Obj-C. The following section provides guidelines on migrating apps written in Obj-C to Swift.

Migrating Obj-C apps to Swift

We strongly suggest reading Apple's guide on migrating from Obj-C to Swift before you proceed.

Ideally, developers should move apps from Obj-C to Swift in one action. However, we realize that many Kinvey developers have large apps written in Obj-C that will take time to migrate to Swift. For such apps, one approach is to convert them in phases. As an example, we ported one of our 1.x samples written in Obj-C to Swift. You can see how we did it here. Below are the steps we followed:

  • Change the dependencies of your project to use the latest Kinvey (3.x) framework instead of KinveyKit (1.x). If you are using CocoaPods to manage dependencies, this change will be made in your Podfile.

  • In v3, all model classes need to be written in Swift. Identify the classes that represent the model in your app (anything that implements KCSPersistable), and move them to Swift. For each model class, follow the DataStore guide to define the mappings to your backend. In the sample app, notice how TestObject.h and TestObject.m have been replaced by TestObject.swift.

  • Create a new Swift file that will bridge the Obj-C code from your app to the Kinvey v3. This file will provide a wrapper for the Kinvey APIs that you need to port. The v3 API uses Swift concepts like generics, which are not portable to Obj-C. One key example of this is the DataStore<T> class. The wrapper is responsible for proxying to the Swift API and supplying the additional type information. In the sample app, the Kinvey.swift file acts as the wrapper.

  • For each Swift API that you need to call from Obj-C, add a method in the wrapper class that translates the Swift API to Obj-C. Here is one example from the sample app that wraps the DataStore<T>.save() API:

let dataStore = DataStore<TestObject>.collection(.network)

func save(_ testObject: TestObject, completionHandler: ((TestObject?, Swift.Error?) -> Void)?) -> KinveyRequest {
    return KinveyRequest(dataStore.save(testObject, completionHandler: completionHandler))
}

Moving your model and Kinvey API calls using this wrapper approach will allow you to keep a large part of the UI and controller code in Obj-C, until you are ready to migrate it.

The following sections assume that you are able to use Swift with v3.

Initialize Client

The first step in using Kinvey's library is to initialize the Client.

1.x

[[KCSClient sharedClient] initializeKinveyServiceForAppKey:@"<#My App Key#>"
                                             withAppSecret:@"<#My App Secret#>" 
                                              usingOptions:nil];
KCSClient.sharedClient().initializeKinveyServiceForAppKey(
    "<#My App Key#>",
    withAppSecret: "<#My App Secret#>",
    usingOptions: nil
)
KCSClient.shared().initializeKinveyService(
    forAppKey: "<#My App Key#>",
    withAppSecret: "<#My App Secret#>",
    usingOptions: nil
)

3.x

Kinvey.sharedClient.initialize(
    appKey: "<#Your app key#>",
    appSecret: "<#Your app secret#>"
)

Note the difference in using sharedClient as a property in v3 vs. as a method in v1.

Using a dedicated instance

For Dedicated Kinvey customers, you'll need to set additional values to inform the subdomain of your dedicated Kinvey Host.

1.x

KCSClientConfiguration* config = [KCSClientConfiguration configurationWithAppKey:@"<#My App Key#>"
                                                                          secret:@"<#My App Secret#>"];
config.serviceHostname = @"<#My Host URL Subdomain#>"; //only the subdomain, so `foo-bass` if your Host URL is `https://foo-baas.kinvey.com`
[[KCSClient sharedClient] initializeWithConfiguration:config];
let config = KCSClientConfiguration(
    appKey: "<#My App Key#>",
    secret: "<#My App Secret#>"
)
config.serviceHostname = "<#My Host URL Subdomain#>" //only the subdomain, so `foo-bass` if your Host URL is `https://foo-baas.kinvey.com`
KCSClient.sharedClient().initializeWithConfiguration(config)
let config = KCSClientConfiguration(
    appKey: "<#My App Key#>",
    secret: "<#My App Secret#>"
)
config?.serviceHostname = "<#My Host URL Subdomain#>" //only the subdomain, so `foo-bass` if your Host URL is `https://foo-baas.kinvey.com`
KCSClient.shared().initialize(with: config)

3.x

//Here we set the apiHostName to a sample url.
//Replace the sample url with the url of your dedicated Kinvey backend.
Kinvey.sharedClient.initialize(
    appKey: "<#Your app key#>",
    appSecret: "<#Your app secret#>",
    apiHostName: NSURL(string: "<#https://my-url.kinvey.com/#>")!
)
//Here we set the apiHostName to a sample url.
//Replace the sample url with the url of your dedicated Kinvey backend.
Kinvey.sharedClient.initialize(
    appKey: "<#Your app key#>",
    appSecret: "<#Your app secret#>",
    apiHostName: URL(string: "<#https://my-url.kinvey.com/#>")!
)

User

You need an active user before interacting with any data from your backend.

In 1.x, the API call [KCSUser activeUser] checks for an active user. This API has been replaced in 3.x by Kinvey.sharedClient.activeUser. A value of nil` indicates that there is no active user in the app.

1.x

if (![KCSUser activeUser]) {
    //show log-in views
} else {
    //user is logged in and will be loaded on first call to Kinvey
}
if KCSUser.activeUser() == nil {
    //show log-in views
} else {
    //user is logged in and will be loaded on first call to Kinvey
}
if KCSUser.active() == nil {
    //show log-in views
} else {
    //user is logged in and will be loaded on first call to Kinvey
}

3.x

if let user = client.activeUser {
    //show log-in views
} else {
    //user is logged in and will be loaded on first call to Kinvey
}

Using Kinvey Auth

If you are using Kinvey as your identity provider, migrate your login and signup calls as shown below.

Signup

1.x

// Create a new user with the username 'kinvey' and the password '12345'
[KCSUser userWithUsername:@"kinvey" password:@"12345" fieldsAndValues:nil withCompletionBlock:^(KCSUser *user, NSError *errorOrNil, KCSUserActionResult result) {
    if (errorOrNil == nil) {
        //user is created
    } else {
        //there was an error with the create
    }
}];
// Create a new user with the username 'kinvey' and the password '12345'
KCSUser.userWithUsername(
    "kinvey",
    password: "12345",
    fieldsAndValues: nil
) { user, errorOrNil, result in
    if errorOrNil == nil {
        //user is created
    } else {
        //there was an error with the create
    }
}
// Create a new user with the username 'kinvey' and the password '12345'
KCSUser.user(
    withUsername: "kinvey",
    password: "12345",
    fieldsAndValues: nil
) { user, errorOrNil, result in
    if errorOrNil == nil {
        //user is created
    } else {
        //there was an error with the create
    }
}

3.x

// Create a new user with the username 'kinvey' and the password '12345'
User.signup(username: "kinvey", password: "12345") { user, error in
    if let user = user {
        //user is created
    } else {
        //there was an error with the create
    }
}

Login

1.x

[KCSUser loginWithUsername:@"kinvey" password:@"12345" withCompletionBlock:^(KCSUser *user, NSError *errorOrNil, KCSUserActionResult result) {
    if (errorOrNil ==  nil) {
        //the log-in was successful and the user is now the active user and credentials saved
        //hide log-in view and show main app content
    } else {
        //there was an error with the login call
   }
}];
KCSUser.loginWithUsername(
    "kinvey",
    password: "12345"
) { user, errorOrNil, result in
    if errorOrNil == nil {
        //the log-in was successful and the user is now the active user and credentials saved
        //hide log-in view and show main app content
    } else {
        //there was an error with the login call
    }
}
KCSUser.login(
    withUsername: "kinvey",
    password: "12345"
) { user, errorOrNil, result in
    if errorOrNil == nil {
        //the log-in was successful and the user is now the active user and credentials saved
        //hide log-in view and show main app content
    } else {
        //there was an error with the login call
    }
}

3.x

User.login(username: "kinvey", password: "12345") { user, error in
    if let user = user {
        //the log-in was successful and the user is now the active user and credentials saved
        //hide log-in view and show main app content
        print("User: \(user)")
    } else if let error = error as? NSError {
        //there was an error
    }
}

Using Mobile Identity Connect

If you are using Mobile Identity Connect as the authentication provider, migrate your login call as shown below.

1.x

[KCSUser presentMICLoginViewControllerWithRedirectURI:@"myRedirectUri://"
                                      withCompletionBlock:^(KCSUser *user, NSError *errorOrNil, KCSUserActionResult result)
{
    if (user) {
        //we have a valid active user
    } else {
        //something went wrong!
    }
}];
KCSUser.presentMICLoginViewControllerWithRedirectURI("myRedirectUri://") { (user, error, actionResult) in
    if let user = user {
        //we have a valid active user
        print("User: \(user)")
    } else {
        //something went wrong!
    }
}
KCSUser.presentMICLoginViewController(withRedirectURI: "myRedirectUri://") { (user, error, actionResult) in
    if let user = user {
        //we have a valid active user
        print("User: \(user)")
    } else {
        //something went wrong!
    }
}

3.x

let redirect = NSURL(string: "<#myRedirectUri://#>")! //this uri must match one of the uris configured in your app's "Auth Source"
User.presentMICViewController(redirectURI: redirect) { user, error in
    if (user != nil) {
        //logged in successfully
    } else if (error != nil) {
        //something went wrong if the error object is not nil
    } else {
        //should never happen!
    }
}
let redirect = URL(string: "<#myRedirectUri://#>")! //this uri must match one of the uris configured in your app's "Auth Source"
User.presentMICViewController(redirectURI: redirect) { user, error in
    if (user != nil) {
        //logged in successfully
    } else if (error != nil) {
        //something went wrong if the error object is not nil
    } else {
        //should never happen!
    }
}

Using Social Auth

If you are using a Social authentication provider (Facebook, Twitter, LinkedIn or Salesforce), migrate your login call as shown below.

1.x

[KCSUser loginWithSocialIdentity:KCSSocialIDFacebook //or KCSSocialIDTwitter, or KCSSocialIDLinkedIn, or KCSSocialIDSalesforce
                accessDictionary:@{ KCSUserAccessTokenKey : accessToken }
             withCompletionBlock:^(KCSUser *user, NSError *errorOrNil, KCSUserActionResult result) {
    if (errorOrNil) {
        //handle error
    }
}];
KCSUser.loginWithSocialIdentity(
    .SocialIDFacebook, //.SocialIDTwitter, or .SocialIDLinkedIn, or .SocialIDSalesforce
    accessDictionary: [ KCSUserAccessTokenKey : accessToken ]
) { (user, error, actionResult) in
    if let error = error {
        //handle error
        print("Error: \(error)")
    }
}
KCSUser.login(
    withSocialIdentity: .socialIDFacebook, //.socialIDTwitter, or .socialIDLinkedIn, or .socialIDSalesforce
    accessDictionary: [ KCSUserAccessTokenKey : accessToken ]
) { (user, error, actionResult) in
    if let error = error {
        //handle error
        print("Error: \(error)")
    }
}

3.x

//Login with Facebook
let facebookDictionary: [String : AnyObject] = <#...#>
User.login(authSource: .Facebook, facebookDictionary) { user, error in
    if let user = user {
        //success
        print("User: \(user)")
    } else {
        //fail
    }
}
//Login with Facebook
let facebookDictionary: [String : Any] = <#...#>
User.login(authSource: .facebook, facebookDictionary) { user, error in
    if let user = user {
        //success
        print("User: \(user)")
    } else {
        //fail
    }
}

For details, refer to User

Push

To migrate your push implementation, you will need to migrate the API calls to register for notications and receive remote notifications.

Registering For Push Notifications

In version 1.x of the library, registering for push is achieved by override multiple methods in your app delegate class. In 3.x, we simplified this to a single method.

1.x

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //Your App Setup...

    [[KCSClient sharedClient] initializeKinveyServiceForAppKey:@"<#My App Key#>"
                                                 withAppSecret:@"<#My App Secret#>" 
                                                  usingOptions:nil];

    //Start push service
    [KCSPush registerForPush];
    return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    [[KCSPush sharedPush] application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken completionBlock:^(BOOL success, NSError *error) {
        //if there is an error, try again later        
    }];
    // Additional registration goes here (if needed)
}

- (void) application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    [[KCSPush sharedPush] application:application didFailToRegisterForRemoteNotificationsWithError:error];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [[KCSPush sharedPush] registerForRemoteNotifications];
    //Additional become active actions
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    [[KCSPush sharedPush] onUnloadHelper];
    // Additional termination actions
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
    //Your App Setup...

    KCSClient.sharedClient().initializeKinveyServiceForAppKey(
        "<#My App Key#>",
        withAppSecret: "<#My App Secret#>",
        usingOptions: nil
    )

    KCSPush.registerForPush()

    return true
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
    KCSPush.sharedPush().application(
        application,
        didRegisterForRemoteNotificationsWithDeviceToken: deviceToken
    ) { success, error in
        //if there is an error, try again later
    }
    // Additional registration goes here (if needed)
}

func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
    KCSPush.sharedPush().application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}

func applicationDidBecomeActive(application: UIApplication) {
    KCSPush.sharedPush().registerForRemoteNotifications()
    //Additional become active actions
}

func applicationWillTerminate(application: UIApplication) {
    KCSPush.sharedPush().onUnloadHelper()
    // Additional termination actions
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
    //Your App Setup...

    KCSClient.shared().initializeKinveyService(
        forAppKey: "<#My App Key#>",
        withAppSecret: "<#My App Secret#>",
        usingOptions: nil
    )

    KCSPush.registerForPush()

    return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    KCSPush.shared().application(
        application,
        didRegisterForRemoteNotificationsWithDeviceToken: deviceToken
    ) { success, error in
            //if there is an error, try again later
    }
    // Additional registration goes here (if needed)
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    KCSPush.shared().application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}

func applicationDidBecomeActive(_ application: UIApplication) {
    KCSPush.shared().registerForRemoteNotifications()
    //Additional become active actions
}

func applicationWillTerminate(_ application: UIApplication) {
    KCSPush.shared().onUnloadHelper()
    // Additional termination actions
}

3.x

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    //Your App Setup...
    Kinvey.sharedClient.initialize(appKey: "<#Your app key#>", appSecret: "<#Your app secret#>")
    Kinvey.sharedClient.push.registerForPush()
    return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
    //Your App Setup...
    Kinvey.sharedClient.initialize(appKey: "<#Your app key#>", appSecret: "<#Your app secret#>")
    Kinvey.sharedClient.push.registerForPush()
    return true
}

Receiving Remote Notifications

The following snippets show how to receive push notifications:

1.x

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
{
    [[KCSPush sharedPush] application:application didReceiveRemoteNotification:userInfo];
    // Additional push notification handling code should be performed here
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    KCSPush.sharedPush().application(application, didReceiveRemoteNotification: userInfo)
    // Additional push notification handling code should be performed here
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
    KCSPush.shared().application(application, didReceiveRemoteNotification: userInfo)
    // Additional push notification handling code should be performed here
}

3.x

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    // Push Notification handling code should be performed here
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
    // Push Notification handling code should be performed here
}

App Badge Management

1.x

//set the badge
[[KCSPush sharedPush] setPushBadgeNumber:100]; //sets to 100

//clear the badge
[[KCSPush sharedPush] resetPushBadge];
//set the badge
KCSPush.sharedPush().setPushBadgeNumber(100) //sets to 100

//clear the badge
KCSPush.sharedPush().resetPushBadge()
//set the badge
KCSPush.shared().setPushBadgeNumber(100) //sets to 100

//clear the badge
KCSPush.shared().resetPushBadge()

3.x

//set the badge
Kinvey.sharedClient.push.badgeNumber = 100 //sets to 100

//clear the badge
Kinvey.sharedClient.push.resetBadgeNumber() //sets badgeNumber to 0

For details, please refer to our Push Guide.

Data Store

Object Mapping

In v3.x, we have changed the way your model classes map to your collections. Each entity in your model should now subclass the Entity class. This replaces the KCSPersistable protocol in v1.x.

1.x

@interface Event : NSObject <KCSPersistable>

@property (nonatomic, copy) NSString* entityId; //Kinvey entity _id
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSDate* date;
@property (nonatomic, copy) NSString* location;
@property (nonatomic, retain) KCSMetadata* metadata; //Kinvey metadata, optional

@end

@implementation Event

- (NSDictionary *)hostToKinveyPropertyMapping
{
    return @{
        @"entityId" : KCSEntityKeyId, //the required _id field
        @"name" : @"name",
        @"date" : @"date",
        @"location" : @"location",
        @"metadata" : KCSEntityKeyMetadata //optional _metadata field
    };
}

@end
class Event : NSObject {    //all NSObjects in Kinvey implicitly implement KCSPersistable

    var entityId: String? //Kinvey entity _id
    var name: String?
    var date: NSDate?
    var location: String?
    var metadata: KCSMetadata? //Kinvey metadata, optional

    override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
        return [
            "entityId" : KCSEntityKeyId, //the required _id field
            "name" : "name",
            "date" : "date",
            "location" : "location",
            "metadata" : KCSEntityKeyMetadata //optional _metadata field
        ]
    }

}
class Event : NSObject {    //all NSObjects in Kinvey implicitly implement KCSPersistable

    var entityId: String? //Kinvey entity _id
    var name: String?
    var date: NSDate?
    var location: String?
    var metadata: KCSMetadata? //Kinvey metadata, optional

    override func hostToKinveyPropertyMapping() -> [AnyHashable : Any]! {
        return [
            "entityId" : KCSEntityKeyId, //the required _id field
            "name" : "name",
            "date" : "date",
            "location" : "location",
            "metadata" : KCSEntityKeyMetadata //optional _metadata field
        ]
    }

}

3.x

class Event: Entity {

    dynamic var name: String?
    dynamic var date: NSDate?
    dynamic var location: String?

    override class func collectionName() -> String {
        //return the name of the backend collection corresponding to this entity
        return "Events"
    }

    //Map properties in your backend collection to the members of this entity
    override func propertyMapping(map: Map) {
        //This maps the "_id", "_kmd" and "_acl" properties
        super.propertyMapping(map)

        //Each property in your entity should be mapped using the following scheme:
        //<member variable> <- ("<query property name>", map["<backend property name>"])
        name <- ("name", map["name"])
        date <- ("date", map["date"], KinveyDateTransform()) //use a transform when needed
        location <- ("location", map["location"])
    }    
}
class Event: Entity {

    dynamic var name: String?
    dynamic var date: Date?
    dynamic var location: String?

    override class func collectionName() -> String {
        //return the name of the backend collection corresponding to this entity
        return "Events"
    }

    //Map properties in your backend collection to the members of this entity
    override func propertyMapping(_ map: Map) {
        //This maps the "_id", "_kmd" and "_acl" properties
        super.propertyMapping(map)

        //Each property in your entity should be mapped using the following scheme:
        //<member variable> <- ("<query property name>", map["<backend property name>"])
        name <- ("name", map["name"])
        date <- ("date", map["date"], KinveyDateTransform()) //use a transform when needed
        location <- ("location", map["location"])
    }    
}

Initializing a Data Store

In v3.x, an instance of the DataStore class represents a collection on your backend.

1.x

KCSCollection* collection = [KCSCollection collectionFromString:@"Events"
                                                        ofClass:[Event class]];
KCSAppdataStore* dataStore = [KCSAppdataStore storeWithCollection:collection
                                                          options:nil];
let collection = KCSCollection(fromString: "Events", ofClass: Event.self)
let dataStore = KCSAppdataStore(collection: collection, options: nil)
let collection = KCSCollection(from: "Events", of: Event.self)
let dataStore = KCSAppdataStore(collection: collection, options: nil)!

3.x

let dataStore = DataStore<Event>.collection()

Saving

To either create or update objects, you should call save()

1.x

[dataStore saveObject:event
  withCompletionBlock:^(NSArray *objectsOrNil, NSError *errorOrNil) {
        if (errorOrNil != nil) {
            //save failed
            NSLog(@"Save failed, with error: %@", [errorOrNil localizedFailureReason]);
        } else {
            //save was successful
            NSLog(@"Successfully saved event (id='%@').", [objectsOrNil[0] kinveyObjectId]);
        }
} withProgressBlock:nil];
dataStore.saveObject(
    event,
    withCompletionBlock: { objectsOrNil, errorOrNil in
        if let objectsOrNil = objectsOrNil?.first {
            //save was successful
            print("Successfully saved event (id='\(objectsOrNil.kinveyObjectId())').")
        } else {
            //save failed
            print("Save failed, with error: \(errorOrNil.localizedFailureReason)")
        }
    },
    withProgressBlock: nil
)
dataStore.save(
    event,
    withCompletionBlock: { objectsOrNil, errorOrNil in
        if let objectsOrNil = objectsOrNil?.first as? Event {
            //save was successful
            print("Successfully saved event (id='\(objectsOrNil.kinveyObjectId()))').")
        } else if let errorOrNil = errorOrNil as? NSError {
            //save failed
            print("Save failed, with error: \(errorOrNil.localizedFailureReason)")
        }
    },
    withProgressBlock: nil
)

3.x

dataStore.save(event) { event, error in
    if let event = event {
        //save was successful
        print("Successfully saved event (id='\(event)').")
    } else {
        //save failed
        print("Save failed, with error: \(error)")
    }
}

Fetching

By Id

1.x

[dataStore loadObjectWithID:event.entityId withCompletionBlock:^(NSArray *objectsOrNil, NSError *errorOrNil) {
    if (errorOrNil == nil) {
        NSLog(@"successful reload: %@", objectsOrNil[0]); // event updated
    } else {
        NSLog(@"error occurred: %@", errorOrNil);
    }
} withProgressBlock:nil];
dataStore.loadObjectWithID(
    event.entityId,
    withCompletionBlock: { objectsOrNil, errorOrNil in
        if let objectsOrNil = objectsOrNil?.first {
            print("successful reload: \(objectsOrNil)") // event updated
        } else {
            print("error occurred: \(errorOrNil)")
        }
    },
    withProgressBlock: nil
)
dataStore.loadObject(
    withID: event.entityId,
    withCompletionBlock: { objectsOrNil, errorOrNil in
        if let objectsOrNil = objectsOrNil?.first {
            print("successful reload: \(objectsOrNil)") // event updated
        } else {
            print("error occurred: \(errorOrNil)")
        }
    },
    withProgressBlock: nil
)

3.x

dataStore.findById(event.entityId) { (event, error) -> Void in
    if let event = event {
        print("successful reload: \(event)") // event updated
    } else {
        print("error occurred: \(error)")
    }
}
dataStore.find(byId: event.entityId) { event, error in
    if let event = event {
        print("successful reload: \(event)") // event updated        
    } else {
        print("error occurred: \(error)")
    }
}

By Query

Query objects are now represented by the class Query instead of KCSQuery. Query objects are now closer to NSPredicate. In fact, Query objects are a combination of NSPredicate and NSSortDescriptor, plus additional properties like skip and limit used for pagination.

1.x

[dataStore queryWithQuery:[KCSQuery queryOnField:@"name" withExactMatchForValue:@"Bob's birthday party"]
      withCompletionBlock:^(NSArray *objectsOrNil, NSError *errorOrNil) {
    if (errorOrNil != nil) {
        //An error happened, just log for now
        NSLog(@"An error occurred on fetch: %@", errorOrNil);
    } else {
        //List all events
        NSLog(@"Events: %@", objectsOrNil);
    }
} withProgressBlock:nil];
dataStore.queryWithQuery(
    KCSQuery(onField: "name", withExactMatchForValue: "Bob's birthday party"),
    withCompletionBlock: { objectsOrNil, errorOrNil in
        if errorOrNil != nil {
            //An error happened, just log for now
            print("An error occurred on fetch: \(errorOrNil)");
        } else {
            //List all events
            print("Events: \(objectsOrNil)");
        }
    },
    withProgressBlock: nil
)
dataStore.query(
    withQuery: KCSQuery(onField: "name", withExactMatchForValue: "Bob's birthday party" as NSString),
    withCompletionBlock: { objectsOrNil, errorOrNil in
        if errorOrNil != nil {
            //An error happened, just log for now
            print("An error occurred on fetch: \(errorOrNil)");
        } else {
            //List all events
            print("Events: \(objectsOrNil)");
        }
    },
    withProgressBlock: nil
)

3.x

let query = Query(format: "name == %@", "Bob's birthday party")
dataStore.find(query) { events, error in
    if let events = events {
        //bob's birthday is probably the first element of the array
    } else {
        //something went wrong!
    }
}

Deleting

In 3.x you can remove objects by reference, id or query.

1.x

[dataStore removeObject:eventToDelete withDeletionBlock:^(NSDictionary* deletionDictOrNil, NSError *errorOrNil) {
    if (errorOrNil) {
        //error occurred - add back into the list
        NSLog(@"Delete failed, with error: %@ ", errorOrNil);
    } else {
        //delete successful - UI already updated
        NSLog(@"deleted response: %@", deletionDictOrNil);
    }
} withProgressBlock:nil];
dataStore.removeObject(
    eventToDelete,
    withDeletionBlock: { deletionDictOrNil, errorOrNil in
        if errorOrNil != nil {
            //error occurred - add back into the list
            print("Delete failed, with error: \(errorOrNil)")
        } else {
            //delete successful - UI already updated
            print("deleted response: \(deletionDictOrNil)")
        }
    },
    withProgressBlock: nil
)
dataStore.remove(
    eventToDelete,
    withDeletionBlock: { deletionDictOrNil, errorOrNil in
        if errorOrNil != nil {
            //error occurred - add back into the list
            print("Delete failed, with error: \(errorOrNil)")
        } else {
            //delete successful - UI already updated
            print("deleted response: \(deletionDictOrNil)")
        }
    },
    withProgressBlock: nil
)

3.x

dataStore.removeById(eventIdToDelete) { count, error in
    if let count = count, count == 1 {
        //if count is equals to 1 is because we succeed
    } else {
        //something went wrong!
    }
}

Refer to the Data Store guide for details.

Relational Data

Previous major versions of the library include KinveyRefs - a feature for modeling and automatically handling relationships between data entities. This is a useful tool, but suffers from several limitations, such as the limit on number of references, performance during data retrieval etc.

The recommended way to handle data relationships in 3.x is described in Data Modeling guide. The v3 libraries do not implement KinveyRefs, but an equivalent capability of automatically handling relationships is on our roadmap.

Got a question?