Welcome

Welcome to Backendless, a powerful API services platform. The platform can be used to build and run mobile and web applications. Backendless consists of built-in, general purpose API services and also supports deployment of custom, developer-defined services and functions. The built-in API services support common backend tasks and operations such as user login and logout, password recovery, data persistence, file upload, etc. Developer-defined services and functions can be used for custom server-side business logic. The diagram below illustrates major components of the Backendless architecture:

backendless-architecture

The sections below describe individual service APIs. It is recommended to go through the Setup section first to configure your environment.

Quick Start Guide

This guide will help you develop a basic mobile application up and running with the Backendless 4 backend.

 

Once the product is installed, there are two URLs to keep in mind:

1. Backendless Console URL - available at http://HOSTNAME:8080, where HOSTNAME is the IP address or the hostname confirmed/entered during  the installation. It is not recommended to use localhost if you plan to run your application in a device emulator.
2. API Endpoint URL - this is the URL where the server accepts and processes the API requests. The API endpoint URL is the same as console URL with /api appended at the end. For example, http://HOSTNAME:8080/api. When Backendless Pro is running, you can confirm that the API endpoint is working, by entering it in a browser. You should see the following (or similar) response:
api-endpoint-response

 

Follow the steps below to build your first app with Backendless:

1. Login to Backendless Console. The username and password are what you entered during the installation process. If you cannot remember the login, click the Register link on the Backendless Console login screen and create a new developer account.
 
2. Select or create an app. If you do not have any applications in your account, you will be prompted to create one.
 
3. Click the Download Project Template button:
download-proj-template.zoom50
 
4. Double click the iOS icon in the download project template popup, then double click either the Objective-C or the Swift icon to download the project for your app:
ios-templates
 
5. The downloaded ZIP file contains an Xcode project with a sample code written in the language you selected in the previous step. The sample code demonstrates the API for saving a data object in Backendless.

 

6. Before you open the project in Xcode, it is important to install the Backendless pod from cocoapods. Unzip the zip file into a directory. Open a terminal window and change the current directory to the root of the project. Run the following command which will download and install the Backendless SDK for iOS.

 

pod install

 

If you receive the following error from cocoapods, it means you need to update to cocoapods version 1.2:
"None of your spec sources contain a spec satisfying the dependency: `Backendless (= <versionnumber>)`."
 
To upgrade to version 1.2, run the following commands:
rm -rf ~/.cocoapods/repos/master
pod setup --verbose
 

7. Open the <projectname>.xcworkspace file in Xcode. The project is ready to run without any changes. Run the app in a device simulator or on a device. When the application runs successfully, it displays a message in the Xcode log output informing that an object has been saved in Backendless:
 
Object is saved in Backendless. Please check in the console.
 
 
8. To verify the object in Backendless, return to Backendless Console, and click the Data icon. The TestTable table shows the persisted object:
saved-object.zoom50

Client-side Setup

Setting up your development environment consists of two steps: (1) XCode project setup and (2) application setup.

Step 1. XCode Project Setup

Backendless SDK for iOS/Mac OS X includes a library which implements the Backendless APIs. It is recommended to use Cocoapods to configure an Xcode project to use Backendless:

The simplest way to create an Xcode project is by using a Backendless Project Template. See the Quick Start section of this guide for instructions.

CocoaPods manages library dependencies for your Xcode projects. The dependencies for the projects are specified in a plain text file called a Podfile. CocoaPods will resolve dependencies between libraries, fetch the resulting source code, then link it together in an Xcode workspace to build your project. Make sure you have the Cocoapods installed your system as described in the CocoaPods documentation.

 

To create a new project with Backendless Pod, follow the steps below:

 

1. Create a new project in Xcode as you normally would, then close the project.
2. Open a Terminal window, and change the current directory to be the project's directory.
3. Run the following command in the Terminal window, which will create a file with the name Podfile.

pod init

4. Open the created Podfile using a text editor and add the following text inside of the target block:

pod 'Backendless', '~> 4.0'

5. Save Podfile, return to the Terminal window and run the following command:

$ pod install

6. Once the pod is downloaded, Xcode project workspace file will be created. This should be the file you use to open the project in Xcode.
7. If you develop with Swift, you will need to add a Swift bridging header. To do that, click the root node in the Project Structure and select the Build Settings section. Locate the Swift Compiler - General section. Enter the following value into the Objective-C Bridging Header field:

Pods/Backendless/SDK/ios/backendless/include/Backendless-Bridging-Header.h

8. Open .xcworkspace file to launch your project, and build it.

Step 2. Application Setup

Once your project is configured, make sure to add Backendless application ID and API key:

1. Login to Backendless Console, select your app, click Manage and copy the Backendless application ID and API key for iOS:
app-id-ios-key.zoom80

 

2. Add Backendless app initialization code into your app. Make sure to replace APP_ID and API_KEY with specific values.

 
Objective-C (AppDelegate.m):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [backendless initApp:APP_ID apiKey:API_KEY];
 
  return YES;
}

Swift (AppDelegate.swift):

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
 
 let APP_ID = "YOUR-APPLICATION-ID"
 let API_KEY = "YOUR-APPLICATION-IOS-API-KEY"
 
 var backendless = Backendless.sharedInstance()
 
 var window: UIWindow?
 
 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
   backendless.initApp(APP_ID, apiKey:API_KEY)
 
   return true
 }
}

 

At this point the client application is configured and is ready to communicate with the backend.

 

Migration from 3.x to 4.x

Version 4 of Backendless introduces some changes in the client-side API. This section of the developer guide describes the changes which should be made in the client application to migrate it to Backendless version 4.x.

 

The general process of application migration from 3.x to 4.0 is:

1. Create an app in the version 4 of Backendless.
2. Migrate data and files from the 3.x application.
3. Make changes in the API usage in the cloud application (if applicable).

Version Differences

Functional Area

Version 3.x

Version 4.0

Application identification (REST Only)

REST requests must include the application-id and secret-key HTTP headers with the API requests.

Application ID and API key (previously known as secret key) are included into the endpoint URL.

Application initialization API.
This is a mandatory  method call required to initialize a native SDK implementation in a client app. Applies to all SDKs except for REST.

the initApp argument list includes version number.

the initApp argument list does not include version number. See Migrating App Initialization API for additional information.

Data Query.

The API for configuring data query parameters such as sorting, paging and identifying columns of the related objects to return in the response.

Data retrieval API (the "find" operation in the Data service) uses the BackendlessDataQuery

BackendlessDataQuery has been replaced with DataQueryBuilder

 

See Migrating Data Query API for additional details

Retrieval of data collections.
This applies to retrieving data objects, geopoints and listing of files.

Native SDKs
All SDK implementations use the BackendlessCollection class to represent a collection of data from the backend. The class provided a paging mechanism enabling retrieval of additional pages of data.

Native SDK and REST

BackendlessCollection is completely eliminated. Instead the backend returns an array of objects (represented as a native collection in the native SDKs). The totalObjects property is replaced with a separate API (see below)

REST
REST operations returned a JSON structure containing an array of objects (paged) as well as the totalObjects property

Object counting.
This is a mechanism used by the server to calculate:

total number of objects in a table, optionally satisfying a search query.
total number of files in a directory listing, optionally matching a pattern.
total number of geopoints in a category, optionally satisfying search query (search in radius, rectangular area and/or search query).

Object counting is coupled with collection retrieval (see the row above). Total number of data objects/files/geopoints is returned to the client through the totalObjects property in the response object (BackendlessCollection for native SDKs)

Since BackendlessCollection is removed and thus is never returned in the API operations, there is a new API to obtain object/file/geopoint counts.

 

See Migrating Object/File counts API for additional information.

Object relationship management.
This is a mechanism for establishing or breaking down relations between two or more data objects using the API. Once a relationship is established, client application can use data retrieval API to fetch related object(s) for a parent object.

To establish a relationship between a parent object and the related children, client application must save the parent object instance which references the child object(s). Backendless processes the entire object hierarchy, determines which child objects must be saved/updated, persists them and establishes object relationship between the parent and the children. To remove a relationship, parent object instance must be fetched from the server with its related child objects. Client app deleted from the collection any children for which the relationship must be removed and saves back to the server the parent object.

New API is introduced for:

1. establishing a relationship between two or more objects. See the "Set" API.
2. adding related objects to an existing relation. See the "Add" API.
3. removing objects from a relation. See the relation removal API.

 

See Migrating Related Objects API for additional information.

 

Migrating App Data

The process of migrating application data and files from 3.x to 4.0 is:

 

1. Export application from Backendless Cloud. Login to Backendless Console for your Cloud-based backend and select the app. You will see the "Migrate to Backendless 4.0" button:
migrate-button.zoom79
 
2. Click the Migrate to Backendless 4.0 button. You will see the following popup. If you have more than one version in your Backendless Cloud backend, make sure to select the version for which Backendless will prepare a migration archive. The reason a version must be selected is because 4.0 uses the one-version-per-app approach:
migration-popup
 
3. Select a version and click "Create migration archive for 4.0". The first step in replicating your app in 4.0 is creating a "migration archive". Backendless creates two archives, one with data and another with files. The data archive contains the entire backend. It includes, application settings, data table schemas, data objects, users, security roles, all security permissions, business logic, geopoints and geofences.  To initiate creation of the migration archives, click the "Create migration archive for 4.0" button.
 
4. Download archives. Backendless starts preparing the migration archive and will send out an email as soon as the data and file archives are ready. The archives are placed into the /migration-to-4 directory in the File storage of your app. You will see two files with the names matching the following patterns:

<versionName>-data-<timestamp>.zip
<versionName>-files-<timestamp>.zip

 

5. Create an application in the version 4.0 of the Backendless. You can do that by switching to version 4 (see the arrows icon next to the Backendless logo in the developer console) and creating an app there.
6. Upload file archive into 4.0 app. Open Backendless Console for 4.0 and select the app where you will import the data/files. It is recommended to start with a blank/new app to avoid any conflicts during the import. Select the Files section in Backendless console and upload the "files" archive into the root directory. This can be done either by using the Upload File menu or by drag-and-drop of the file into Console. Once the file is uploaded, the console will appear similar to the image below:
uploaded-file-archive.zoom84
 
7. Upload "Data" archive into 4.0 app. Click the Manage icon, then click Import. Click the Browse button located next to "Backendless app" and browse to the "data" archive downloaded from Backendless Cloud. Once the file is selected, click the IMPORT button at the bottom of the screen to initiate the import process:
start-import-v4.zoom56
 
8. Migration is complete. Once Backendless 4.0 finishes processing the data, it will send out an email informing you of the results. You can see the migration progress or its status after it is complete in the migration log available in the Files section of your app:
migration-logv4.zoom90

App Initialization

Support for application versions has been removed in Backendless v4. As an alternative to versions, it is recommended to create multiple copies of the same application and export/import data between the apps. As a result of the version support removal, application initialization in the code does not require version name in the initApp call.

Version 3.x:

[[Backendless sharedInstance] initApp:(NSString *)applicationID 
                                       secret:(NSString *)secret 
                                       version:(NSString *)version];
Backendless.sharedInstance().initApp(applicationID: String, 
                                     secret: String, 
                                     version: String)

Version 4.x:

[[Backendless sharedInstance] initApp:(NSString *)applicationId 
                                       APIKey:(NSString *)apiKey];
Backendless.sharedInstance().initApp(applicationId: String, 
                                     apiKey: String)

Notice that version 4 uses the term "API key" for the concept we used to call "secret key".

Data Query

Data service find operation can accept an argument with additional options instructing the server to sort data, retrieve paged results and return related child objects. The example below demonstrates the usage of the data query argument in versions 3.x and 4.x:

Version 3.x:

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];
 
BackendlessDataQuery *dataQuery = [BackendlessDataQuery new];
// set where clause
[dataQuery setWhereClause:@"age > 30"];
 
QueryOptions *queryOptions = [QueryOptions new];
 
// set related columns
[queryOptions addRelated:@"address"];
[queryOptions addRelated:@"preferences"];
 
// request sorting
NSArray<NSString *> *sortBy = @[@"name"];
[queryOptions setSortBy:sortBy];
 
// set offset and page size
[queryOptions setPageSize:@20];
[queryOptions setOffset:@40];
 
// set query options into data query
[dataQuery setQueryOptions:queryOptions];
 
[contactStorage find:dataQuery
            response:^(BackendlessCollection *contactObjects) {
                NSLog(@"Retrieved %i objects", (int)[contactObjects getCurrentPage].count);
            }
               error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault.message);
               }];
let backendless = Backendless.sharedInstance()

let contactStorage = backendless.data.ofTable("Contact")

let dataQuery = BackendlessDataQuery()
// set where clause
dataQuery.whereClause = "age > 30"

let queryOptions = QueryOptions()

// set related columns
queryOptions.addRelated("address")
queryOptions.addRelated("preferences")

// request sorting
let sortBy = ["name"]
queryOptions.sortBy = sortBy

// set offset and page size
queryOptions.pageSize = 20
queryOptions.offset = 40

// set query options into data query
dataQuery.queryOptions = queryOptions

contactStorage.find(dataQuery,
        response: {
          (contactObjects: BackendlessCollection?) -> () in
          print("Retrieved \(String(describing: contactObjects?.getCurrentPage().count)) objects")
        },
        error: {
          (fault: Fault?) -> () in
          print("Server reported an error: \(String(describing: fault?.message))")
       })

Version 4.x:

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];

DataQueryBuilder *queryBuilder = [DataQueryBuilder new];

// set where clause
[queryBuilder setWhereClause:@"age > 30"];

// set related columns
[queryBuilder addRelated:@"address"];
[queryBuilder addRelated:@"preferences"];

// request sorting
NSArray<NSString *> *sortBy = @[@"name"];
[queryBuilder setSortBy:sortBy];

// set offset and page size
[queryBuilder setPageSize:20];
[queryBuilder setOffset:40];

[contactStorage find:queryBuilder
            response:^(NSArray *contactObjects) {
                NSLog(@"Retrieved %i objects", (int)contactObjects.count);
            }
               error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault.message);
               }];
let backendless = Backendless.sharedInstance()

let contactStorage = backendless.data.ofTable("Contact")
 
let queryBuilder = DataQueryBuilder()
 
// set where clause
queryBuilder.setWhereClause("age > 30")
 
// set related columns
queryBuilder.addRelated("address")
queryBuilder.addRelated("preferences")
 
// requesting sorting
let sortBy = ["name"]
queryBuilder.setSortBy(sortBy)
 
// set offset and page size
queryBuilder.setPageSize(20)
queryBuilder.setOffset(40)
 
contactStorage.find(queryBuilder,
   response: {
     (contactObjects: [Any]?) -> () in
     print("Retrieved \(String(describing: contactObjects?.count)) objects")
   },
   error: {
     (fault: Fault?) -> () in
     print("Server reported an error: \(String(describing: fault?.message))")
   })

 

Additional resources:

Related Objects

Establishing object relationship:

The process of establishing a relationship between multiple objects in Backendless 3.x is by saving the parent object which references its child(ren) in the same object hierarchy/tree. Version 4 changes the mechanism by introducing a dedicated operation which establishes a relationship between the objects. The example below stores two objects in Backendless Data service: Contact and Address and then sets up a relationship between them:

Version 3.x:

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];
id<IDataStore> addressStorage = [backendless.data ofTable:@"Address"];

NSDictionary *joeThePlumber = @{@"name" : @"Joe",
                                @"age" : @27,
                                @"phone" : @"1-972-5551212",
                                @"title" : @"Plumber"};

NSDictionary *address = @{@"street" : @"123 Main St.",
                          @"city" : @"Denver",
                          @"state" : @"Colorado"};

NSMutableDictionary *savedContact = [contactStorage save:joeThePlumber];
NSDictionary *savedAddress = [addressStorage save:address];

NSArray *addresses = @[savedAddress];
[savedContact setValue:addresses forKey:@"addresses"];
[contactStorage save: savedContact];
let backendless = Backendless.sharedInstance()

let contactStorage = backendless.data.ofTable("Contact")
let addressStorage = backendless.data.ofTable("Address")
        
let joeThePlumber: [String : Any] = ["name" : "Joe",
                                     "age" : 27,
                                     "phone" : "1-972-5551212",
                                     "title" : "Plumber"]
        
let address = ["street" : "123 Main St.",
               "city" : "Denver",
               "state" : "Colorado"]
        
var savedContact = contactStorage.save(joeThePlumber)!
let savedAddress = addressStorage.save(address)
        
let addresses = [savedAddress]
savedContact["addresses"] = addresses
        
contactStorage.save(savedContact)

Version 4.x:

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];
id<IDataStore> addressStorage = [backendless.data ofTable:@"Address"];

NSDictionary *joeThePlumber = @{@"name" : @"Joe",
                                @"age" : @27,
                                @"phone" : @"1-972-5551212",
                                @"title" : @"Plumber"};

NSDictionary *address = @{@"street" : @"123 Main St.",
                          @"city" : @"Denver",
                          @"state" : @"Colorado"};

NSDictionary *savedContact = [contactStorage save:joeThePlumber];
NSDictionary *savedAddress = [addressStorage save:address];

NSArray *addresses = @[[savedAddress valueForKey:@"objectId"]];

// for 1:1 relations, use the ":1" notation instead of ":n"
[contactStorage setRelation:@"addresses:Address:n"
             parentObjectId:[savedContact valueForKey:@"objectId"]
               childObjects:addresses];
let backendless = Backendless.sharedInstance()

let contactStorage = backendless.data.ofTable("Contact")
let addressStorage = backendless.data.ofTable("Address")
 
let joeThePlumber: [String : Any] = ["name" : "Joe",
                                     "age" : 27,
                                     "phone" : "1-972-5551212",
                                     "title" : "Plumber"]
 
let address = ["street" : "123 Main St.",
               "city" : "Denver",
               "state" : "Colorado"]
 
let savedContact = contactStorage.save(joeThePlumber) as! [String: Any]
let savedAddress = addressStorage.save(address) as! [String: Any]
let addresses = [savedAddress["objectId"]] as! [String]
 
// for 1:1 relations, use the ":1" notation instead of ":n"
contactStorage.setRelation("addresses:Address:n",
                           parentObjectId: savedContact["objectId"] as! String,
                           childObjects: addresses)

Deleting/breaking object relationship:

Similar to establishing a relationship between objects, Backendless 3.x relied on the update operation which sends the parent object to the server with an updated list of child objects. Any children removed from the collection on the client-side, are removed on the server as a result of the update call. Version 4 introduces a dedicated method to remove a relationship between objects. The example below removes a relationship between a Contact object and the first related Address object:

Version 3.x:

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];

// load parent object
NSDictionary *parentObject = [contactStorage findById:@"XXX" relationsDepth:1];
NSArray *relationNames = @[@"addresses"];
[contactStorage loadRelations:parentObject relations:relationNames];

// create a list of child objects for which to break the relation
NSMutableArray *addresses = [parentObject valueForKey:@"addresses"];
// delete the relation between the parent object and children for the "addresses" column
[addresses removeObjectAtIndex:0];
[parentObject setValue:addresses forKey:@"addresses"];
[contactStorage save:parentObject];
let backendless = Backendless.sharedInstance()

let contactStorage = backendless.data.ofTable("Contact")

// load parent object
var parentObject = contactStorage.find(byId: "XXX", relationsDepth: 1)!
let relationNames = ["addresses"]
contactStorage.loadRelations(parentObject, relations: relationNames)

// create a list of child objects for which to break the relation
var addresses = parentObject["addresses"] as! [[String : Any]]

// delete the relation between the parent object and children for the "addresses" column
addresses.remove(at: 0)
parentObject["addresses"] = addresses
contactStorage.save(parentObject)

Version 4.x:

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];

// load parent object
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:1];
NSDictionary *parentObject = [contactStorage findById:@"XXX" queryBuilder:queryBuilder];

// create a list of child objects for which to break the relation
NSMutableArray *addresses = [parentObject valueForKey:@"addresses"];

NSString *parentId = [parentObject valueForKey:@"objectId"];
NSString *addressId = [addresses[0] valueForKey:@"objectId"];

// delete the relation between the parent object and children for the "addresses" column
[contactStorage deleteRelation:@"addresses"
                parentObjectId:parentId
                  childObjects:@[addressId]];
let backendless = Backendless.sharedInstance()

let contactStorage = backendless.data.ofTable("Contact")
 
// load parent object
let queryBuilder = DataQueryBuilder()!
queryBuilder.setRelationsDepth(1)
let parentObject = contactStorage.find(byId: "XXX", queryBuilder: queryBuilder) as! NSDictionary
 
// create a list of child objects for which to break the relation
let addresses = parentObject["addresses"] as! [[String : Any]]
 
let parentId = parentObject["objectId"] as! String
let addressId = addresses[0]["objectId"] as! String
 
// delete the relation between the parent object and children for the "addresses" column
contactStorage.deleteRelation("addresses", parentObjectId: parentId, childObjects: [addressId])

 

Additional resources:

Object/File Counts

Backendless version 4.x introduces new API to calculate total number of objects and files in a remote collection. In the previous version of Backendless, that number was included as a property of the returned collection. See the examples below:

Version 3.x

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];
BackendlessDataQuery *dataQuery = [BackendlessDataQuery new];
[dataQuery setWhereClause:@"age > 21"];
[contactStorage find:dataQuery
            response:^(BackendlessCollection *foundContacts) {
                NSLog(@"Total number of objects matching query = %@", [foundContacts getTotalObjects]);
            }
               error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault.message);
               }];
let backendless = Backendless.sharedInstance()
let contactstorage = backendless.data.ofTable("Contact")
let dataQuery = BackendlessDataQuery()
dataQuery.whereClause = "age > 21"
contactstorage?.find(dataQuery,
    response: {
      (foundContacts: BackendlessCollection?) -> () in
      print("Total number of objects matching query = \(String(describing: foundContacts?.totalObjects))")
    },
    error: {
      (fault: Fault?) -> () in
      print("Server reported an error: \(String(describing: fault?.message))")
    })

Version 4.x

#define backendless [Backendless sharedInstance]

id<IDataStore> contactStorage = [backendless.data ofTable:@"Contact"];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setWhereClause:@"age > 21"];
[contactStorage getObjectCount:queryBuilder
                      response:^(NSNumber *objectCount) {
                          NSLog(@"There are %@ objects matching the query", objectCount);
                      }
                         error:^(Fault *fault) {
                             NSLog(@"Server reported an error: %@", fault.message);
                         }];
let backendless = Backendless.sharedInstance()

let contactStorage = backendless.data.ofTable("Contact")
let queryBuilder = DataQueryBuilder()!
queryBuilder.setWhereClause("age > 21")
contactStorage?.getObjectCount(queryBuilder,
   response: {
     (objectCount: NSNumber?) -> () in
     print("There are \(String(describing: objectCount)) objects matching the query")                                    
   },
   error: {
     (fault: Fault?) -> () in
     print("Server reported an error: \(String(describing: fault?.message))")
  })

 

Additional resources:

Data/File Paging

Data paging in Backendless 3 is built into the collection returned from the data retrieval operations. Version 4 changes the mechanism by shifting paging operations to the data query class. See the examples below:

Version 3.x

Consider the following data table in Backendless:

sample-data-paging.zoom50
The code for retrieving the data using default page size (10 objects):

#define backendless [Backendless sharedInstance]

-(void)paging {
    [[backendless.persistenceService of:[Restaurant class]]
     find:^(BackendlessCollection *restaurantCollection) {
         NSLog(@"Loaded %i restaurants in the current page", 
               (int)restaurantCollection.getCurrentPage.count);
         for (Restaurant *restaurant in restaurantCollection.getCurrentPage) {
             NSLog(@"%@", restaurant.name);
         }
         [self nextPageAsync:restaurantCollection];
     }
     error:^(Fault *fault) {
         NSLog(@"Server reported an error: %@", fault);
     }];
}

-(void)nextPageAsync:(BackendlessCollection *)restaurantCollection {
    if ([[restaurantCollection getCurrentPage] count] == 0) {
        NSLog(@"Reached the end of collection");
        return;
    }
    [restaurantCollection nextPageAsync:^(BackendlessCollection *restaurants) {
        [self nextPageAsync:restaurants];
      }
      error:^(Fault *fault) {
        NSLog(@"Server reported an error: %@", fault);
      }];
}
let backendless = Backendless.sharedInstance()

func paging() {
  backendless.persistenceService.of(Restaurant.ofClass()).find({
    (restaurantCollection: BackendlessCollection?) -> () in
    print("Loaded \(String(describing: restaurants?.getCurrentPage().count))" +
          " restaurants in the current page")
    for restaurant in (restaurantCollection?.getCurrentPage() as! [Restaurant]) {
      print("\(String(describing: restaurant.name))")
    }
    self.nextPageAsync(restaurantCollection: restaurants!)
  }, 
  error: {
    (fault: Fault?) -> () in
    print("Server reported an error: \(String(describing: fault?.message))")
  })
}

func nextPageAsync(restaurantCollection: BackendlessCollection) {
  if (restaurantCollection.getCurrentPage().count == 0) {
    print("Reached the end of collection")
    return
  }
  restaurantCollection.nextPageAsync({
    (restaurants: BackendlessCollection?) -> () in
    self.nextPageAsync(restaurantCollection: restaurants!)
  },
  error: {
    (fault: Fault?) -> () in
    print("Server reported an error: \(String(describing: fault?.message))")
  })
}
The code produces the following output:

Loaded 10 restaurants in the current page
 Artin's Grill
 Istanbul
 Magic Wok
 Olive Garden
 Platia
 Cantina Loredo
 Wendy's
 Chili's
 Kenny's Burgers
 Tony Roma's
Loaded 1 restaurants in the current page
 McDonnald's
Reached the end of collection

Version 4.x

Identical to the screenshot from 3.x above, consider the following data in version 4 of Backendless:

#define backendless [Backendless sharedInstance]

-(void)paging:(DataQueryBuilder *)queryBuilder {
    [[backendless.data ofTable:@"Restaurant"]
     find:queryBuilder
     response:^(NSArray *restaurantCollection) {
         if (restaurantCollection.count != 0) {
             NSLog(@"Loaded %d restaurants in the current page", (int)restaurantCollection.count);
             for (NSDictionary *restaurant in restaurantCollection) {
                 NSLog(@"%@", [restaurant valueForKey:@"name"]);
             }
             [self paging:[queryBuilder prepareNextPage]];
         }
         else {
             NSLog(@"Reached the end of collection");
         }
     }
     error:^(Fault *fault) {
         NSLog(@"Server reported an error: %@", fault.message);
     }];
}
let backendless = Backendless.sharedInstance()

func paging(queryBuilder: DataQueryBuilder) {
  backendless.data.ofTable("Restaurant").find(
    queryBuilder,
    response: {
      (restaurantCollection: [Any]?) -> () in
      if (restaurantCollection?.count != 0) {
        print("Loaded \(String(describing: restaurantCollection?.count)) restaurants in the current page")
                    
        for restaurant in (restaurantCollection as! [[String:Any]]) {
          print("\(String(describing: restaurant["name"]))")
        }
        self.paging(queryBuilder: queryBuilder.prepareNextPage())
      }
    },
    error: {
      (fault: Fault?) -> () in
      print("Server reported an error: \(String(describing: fault?.message))")
    })
}
The code produces the same output as the code for 3.x

Sync and Async Calls

All Backendless APIs for iOS are available through synchronous (blocking) and asynchronous (non-blocking) methods. All asynchronous (non-blocking) API methods support block-based callbacks. Successful server responses are delivered to the response block. If  there is an error, it is delivered to the error block.  See the Error Handling section for additional information on how to process errors. For additional information on code blocks, see Practical Guide to Blocks.

Error Handling

When the server reports the error, it is delivered to the application as Fault object:

@interface Fault : NSObject 
// fault message
@property (nonatomic, readonly) NSString *message;
// fault detail description
@property (nonatomic, readonly) NSString *detail;
// fault code
@property (nonatomic, readonly) NSString *faultCode;
@end
class Fault : NSObject 
{
  // fault message
  var message : String?

  // fault detail description
  var detail : String?
  
  // error code
  var faultCode : String?
}

 

Delivery of the error from the API into the application varies between synchronous and asynchronous methods.

Error handling with synchronous methods

All synchronous (blocking) operations throw an exception if the server returns an error. Error handling for these methods must be implemented with a try/catch block.

@try {
  // synchronous backendless API call here
}   
@catch (Fault *fault) {
  // handle server-side exception here
}
Types.tryblock({ () -> Void in
  // sychronous backendless API call here
},
catchblock: { (exception) -> Void in
  // handle error from server here
})

Error handling with asynchronous methods

Error handling for errors returned from the server must be implemented in a block-based callback.


Error Codes

Click here for the complete list of Backendless Error Codes

User Service API

Overview

The Backendless User Service provides the functionality related to the user accounts such as user registrations, logins, password recovery and logouts. The core concept which the User Service relies on is the User entity. The structure of the entity is configurable, that is a developer can decide which properties "describe" a user in the context of the application. Typically, properties describing a user are the ones collected during the user registration process.  The User Service provides the API enabling the following functionality for the applications built on top of Backendless:

 

User Registration - Applications use the Backendless' registration API to let the users register and create accounts for subsequent logins. Application developers can restrict access to the server-side resources for specific user accounts or based on roles.
User Login - the API lets the registered users login to establish their identity within the application.
Password Recovery - Backendless supports a complete workflow allowing users to recover lost or forgotten passwords.
User Logout - the API lets the logged in users terminate their session and disassociate their identity from the application.
Updating User Registration - the API supports the operation of updating user information.

Retrieve User Schema

An application can get a list of the properties associated with the user schema by using the following API:

Asynchronous Methods:

#define backendless [Backendless sharedInstance]
[backendless.userService describeUserClass:
                            ^(NSArray<UserProperty*> *)responseBlock 
                            error:^(Fault *)errorBlock];
let backendless = Backendless.sharedInstance()!
backendless.userService.describeUserClass(responseBlock : ([UserProperty] -> Void), 
                                          error: errorBlock : (Fault?) -> Void)

Synchronous Methods:

#define backendles [Backendless sharedInstance]
(NSArray<UserProperty *>)[backendless.userService describeUserClass];
let backendless = Backendless.sharedInstance()!
backendless.userService.describeUserClass() as [UserProperty]

Return Value:

An array of UserProperty objects. The UserProperty class definition: UserProperty

Example:

#define backendles [Backendless sharedInstance]

- (void)retrieveUserSchema {
    [backendless.userService describeUserClass:^(NSArray<UserProperty *> *properties) {
        for (UserProperty *userProp in properties) {
            NSLog(@"Property name - %@", userProp.name);
            NSLog(@"required - %i", userProp.isRequired);
            NSLog(@"identity - %i", userProp.isIdentity);
            NSLog(@"data type - %@", userProp.type);
        }
    }
    error:^(Fault *fault) {
      NSLog(@"Server reported an error: %@", fault.description);
    }];
}
let backendless = Backendless.sharedInstance()!

func retrieveUserSchema () {
   backendless.userService.describeUserClass({
       (properties : [UserProperty]?) -> Void in
       for userProp in properties! {
           print("Property name - \(userProp.name)")
           print("required - \(userProp.isRequired())")
           print("identity - \(userProp.identity)")
           print("data type - \(userProp.type)")
       }
   },
   error: {
     (fault : Fault?) -> Void in
       print("Server reported an error: \(fault?.description)")
   })
}

User Registration

The user registration API can be used to create user accounts in the application. A registration request must provide a user object as a collection of key/value properties. The collection must contain all the required properties which must include a property marked as identity as well as the "password" property. The default identity property is "email", however, it can be changed in Backendless Console. Additionally, the "email" property is required if the application is configured to confirm email addresses for the registered users.

#define backendless [Backendless sharedInstance]

// blocking method
-(BackendlessUser *)backendless.userService.registerUser:(BackendlessUser *)user;

// non-blocking method
-(void)backendless.userService.registerUser:(BackendlessUser *)user 
                                             response:(void(^)(BackendlessUser *))responseBlock 
                                             error:(void(^)(Fault *))errorBlock;
// blocking method
func backendless.userService.register(user : BackendlessUser) -> BackendlessUser

// non-blocking method
func backendless.userService.register(user : BackendlessUser, 
                                      response responseBlock : BackendlessUser) -> Void, 
                                      error errorBlock : (Fault) -> Void)

Return value:

The return value is a BackendlessUser object representing the registered user. The objectId property contains a value assigned by the server. The registration API does not login the user.

Error Codes:

The following errors may occur during User Registration API calls. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Invalid application info (application id or api key)

3009

User registration is disabled for the application

3010

User registration has an unknown property and dynamic properties are disabled for this version of the application

3011

Missing "password" property

3012

Required property is missing

3013

Missing value for the identity property

3014

External registration failed with an error.

3021

General user registration error. Details included with the error message.

3033

User with the same identity already exists

3038

Missing application-id or collection of properties for the registering user

3039

Property "id" cannot be used in the registration call

3040

Email address is in the wrong format

3041

A value for a required property is missing

3043

Duplicate properties in the registration request

8000

Property value exceeds the length limit

 

#define backendless [Backendless sharedInstance]

- (void)userRegistration {
    // do not forget to call Backendless.initApp when your app initializes
    BackendlessUser *user = [BackendlessUser new];
    [user setProperty:@"email" object:@"james.bond@mi6.co.uk"];
    [user setPassword:@"iAmWatchingU"];
    [backendless.userService registerUser:user
               response:^(BackendlessUser *registeredUser) {
                 NSLog(@"User registered: %@", [registeredUser valueForKey:@"email"]);
               }
               error:^(Fault *fault) {
                 NSLog(@"Server reported an error: %@", fault.description);
               }];
}

let backendless = Backendless.sharedInstance()!

func registerUser() {
    // do not forget to call Backendless.initApp when your app initializes
    let user = BackendlessUser()
    user.setProperty("email", object: "james2.bond@mi6.co.uk")
    user.password = "111"
    backendless.userService.register(user,
               response: {
                 (registeredUser : BackendlessUser?) -> Void in
                 print("User registered \(registeredUser?.value(forKey: "email"))")
               },
               error: {
                 (fault : Fault?) -> Void in
                 print("Server reported an error: \(fault?.description)")
               })
}

Turning Registration Off

User registration can be disabled for a particular version of the application using the Backendless Console:

1. Login to the console and select the application.
2. Click the Users icon in the vertical icon menu on the left.
3. Click User Registration.

The Registration toggle turns the registration API on or off. When the registration is turned off and a user attempts to register, the system returns error 3009.

registration-togglev4.zoom70

Email Confirmations

Backendless can send out an email requesting new registered users to confirm their email address. This feature can be configured in the Backendless Console:

1. Log into the console and select the application.
2. Click the Users icon in the vertical icon menu on the left.
3. Click User Registration.

email-confirmationv4.zoom70

When email confirmations are turned on (the feature is disabled by default), the "email" user property is required and must contain a value formatted as an email address. To configure the text of the email message, select Email Templates from the Users menu in the console and select the User made registration event.

Login

Registered users can login to establish their identity with the application using the API below. The login operation requires two properties: one marked as user identity and the second is password. Backendless automatically assigns the "AuthenticatedUser" role to all successfully logged in users. The role can be used to differentiate access to various resources (persistent objects, messaging channels, files) between authenticated users and guests.

 

Asynchronous Methods:

[backendless.userService login:(NSString *)login
                          password:(NSString *)password
                          response:(BackendlessUser *)responseBlock
                             error:(Fault *)errorBlock];
func backendless.userService.login(login: String,
                                     password: String,
                                     response: ((BackendlessUser) -> Void),
                                     error: ((Fault?) -> Void))

where:

login - a value for the property marked as identity.
password - user's password

Synchronous Methods:

[backendless.userService login:(NSString *)login password:(NSString *)password];
func backendless.userService.login(login: String, password: String) -> BackendlessUser

where:

login - a value for the property marked as identity.
password - user's password.

Return value:

The login API returns a BackendlessUser object representing the logged in user. The object has the values for all the properties stored in Backendless database including those for which the "auto-load" option is enabled.

Errors:

The following errors may occur during the Login API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3000

Login has been disabled for the user account.

3001

Missing login settings, possibly invalid application id or version.

3002

User cannot login because Multiple Logins disabled and there is a logged in user for the account.

3003

Invalid login or password.

3006

Either login or password is an empty string value.

3034

User logins are disabled for the version of the application.

3036

Account locked out due to too many failed logins.

3038

One of the required parameters (application id, version, login or password) is null

3044

Multiple login limit for the same user account has been reached.

8000

Property value exceeds the length limit

 

#define backendless [Backendless sharedInstance]

- (void)loginUser {
    [backendless.userService login:@"james.bond@mi6.co.uk"
                          password:@"iAmWatchingU"
                          response:^(BackendlessUser *loggedUser) {
                              NSLog(@"User logged in");
                          }
                             error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault.description);
                             }];
}
let backendless = Backendless.sharedInstance()!

func loginUser () {
        backendless.userService.login("james.bond@mi6.co.uk",
                                      password: "iAmWatchingU",
                                      response: {
                                        (loggedUser : BackendlessUser?) -> Void in
                                        print("User logged in")
        },
                                      error: {
                                        (fault : Fault?) -> Void in
                                        print("Server reported an error: \(fault?.description)")
        })
    }

Remembering User Login

It is a frequent requirement for the applications to store user's login so next time application starts, the user is not required to login. The user interface for this scenario is typically a "Remember me" checkbox. Backendless API supports this use-case through an API call:

-(BOOL)[backendless.userService setStayLoggedIn:(Bool)value];
func backendless.userService.setStayLoggedIn(value: Bool) -> Bool

where:

value - Yes/true to request remembering the login and No/false to require login every time application starts.

The method must be invoked after application has been initialized with the initApp call before the login API is used.

Example:

[backendless.userService setStayLoggedIn:YES];
backendless.userService.setStayLoggedIn(true)

Additionally there is a property in user service, which indicates if the current state of the system is set to allow persistent logins:

@property (readonly) BOOL isStayLoggedIn
let backendless = Backendless.sharedInstance()
let isStayLoggedIn = backendless.userService.isStayLoggedIn

If the persistent logins are enabled, the property returns Yes/true, otherwise No/false.

Validating User Login

The login operation provides a way to persist the user-token on the client side so it can be used when the application is restarted. This helps in streamlining the user experience since the user of the application does not need to login again. However, when the application restarts, it needs to check if the underlying user token, and hence the user session are still valid. This can be accomplished with the API below:

// Synchronous method
-(NSNumber *)backendless.userService.isValidUserToken;

// Asynchronous method 
-(void)backendless.userService.isValidUserToken:(void(^)(NSNumber *))responseBlock 
                                                 error:(void(^)(Fault *))errorBlock;
// Synchronous method
func backendless.userService.isValidUserToken -> NSNumber

// Asynchronous method 
func backendless.userService.isValidUserToken(responseBlock: ((NSNumber?) -> Void), 
                                              error: ((Fault?) -> Void))

Return value:

The server returns a boolean value of true if token is valid, false otherwise.

Example:

[backendless.userService isValidUserToken:^(NSNumber *result) {
      NSLog(@"Is login valid? - ", [result boolValue]);
    }
    error:^(Fault *fault) {
      NSLog(@"Server reported an error: %@", fault.description);
    }];
backendless.userService.isValidUserToken({
    (result : NSNumber?) -> Void in
       print("Is login valid? - \(result.boolValue)")
    },
    error: {
      (fault : Fault?) -> Void in
      print("Server reported an error: \(fault?.description)")
    })

 

Login with Facebook

Backendless integrates with Facebook to support user authentication and login into a Backendless application with a Facebook account. Using the integration application developer can provide a way to skip the application registration step and allow users to use their Facebook identity to register with and experience the application.

 

In order to integrate Facebook login with Backendless, client application must use the Facebook SDK to authenticate the user and then makes a Backendless API call to link the Facebook identity to a BackendlessUser instance.

 

Property Mapping

Backendless User objects may have their own set of user properties. As a result, when a user logs in using their Facebook account, their Facebook Graph API user fields are mapped to the Backendless user properties via "property mapping". There may be multiple mapping, each must reference  a Facebook user field and a name of a Backendless property. Once a user is authenticated, Backendless obtains the field values from the Facebook user graph object and populates the mapped properties in the BackendlessUser object. For the very first login, Backendless also creates the user in its internal system.

Backendless Configuration

A Backendless application (the server side) must be configured with Facebook's application App ID and secret key. See the "Social Settings" chapter of the user guide (the Manage section) for detailed instructions for configuring Backendless with Facebook.

Integration API

The API provides a way to "exchange" Facebook access token for Backendless user object which represents the corresponding Facebook user. Before using the API, application must perform login to Facebook using its SDK. The integration process consists of the following steps:

 

Configure the environment with the Facebook SDK. Complete instructions are available at: https://developers.facebook.com/docs/ios/getting-started
Configure Backendless backend with the Facebook App ID/API Key.

 

When creating an application with uses the Facebook SDK, make sure to follow the steps below:

1. Add Facebook SDK to your project per the instructions from:
https://developers.facebook.com/docs/ios/getting-started
2. Add all the required code to your app which performs login to Facebook and obtains FBSDKAccessToken. The token is all that is required by Backendless in order to login a Facebook user.
3. Once the app obtains the Facebook access token, use the following Backendless API:
#define backendless [Backendless sharedInstance]

// Use the following code to retrieve parameters from 
// FBSDKAccessToken. The FBSDKAccessToken class is available in 
// Facebook Objective-C SDK 
FBSDKAccessToken *token = [FBSDKAccessToken currentAccessToken];
NSString *userId = token.userId;
NSString *tokenString = token.tokenString;
NSString *expirationDate = token.expirationDate;
NSString *fieldsMapping = @{@"email":@"email"};

// synchronous method
-(BackendlessUser *)[backendless.userService loginWithFacebookSDK:(NSString *)userId 
                                             tokenString:(NSString *)tokenString 
                                             expirationDate:(NSDate *)expirationDate 
                                             fieldsMapping:(id)fieldsMapping];
                                             
// asynchronous method
-(void)[backendless.userService loginWithFacebookSDK:(NSString *)userId 
                                             tokenString:(NSString *)tokenString 
                                             expirationDate:(NSDate *)expirationDate 
                                             fieldsMapping:(id)fieldsMapping 
                                             response:^(BackendlessUser *)responseBlock 
                                             error:^(Fault *)errorBlock];
let backendless = Backendless.sharedInstance()

// Use the following code to retrieve parameters from 
// AccessToken. The AccessToken class is available in 
// Facebook Swift SDK 
let token = AccessToken.current!
let userId : String = token.userId
let tokenString : String = token.authenticationToken
let expirationDate : Date = token.expirationDate
let fieldsMapping =  NSDictionary(dictionary:["email" : "email"])

// synchronous method
func backendless.userService.login(withFacebookSDK(userId): String, 
                                   tokenString: String, 
                                   expirationDate: Date, 
                                   fieldsMapping: Any) -> BackendlessUser

// asynchronous method
func backendless.userService.login(withFacebookSDK(userId): String, 
                                   tokenString: String, 
                                   expirationDate:Date, 
                                   fieldsMapping: Any, 
                                   response: ((BackendlessUser) -> Void)responseBlock, 
                                   error: ((Fault) -> Void)errorBlock) -> Void

where:

token                - represents an immutable access token for using Facebook services.

fieldMapping - a mapping between the Facebook fields and Backendless user properties. Keys must be the names of the Facebook fields, values - the names of the properties which will be created in the Users table in Backendles. See the Property Mapping section for additional details.

Login with Twitter

Twitter Login

To enable Twitter account log in to your Backendless application:

1. Create your application at https://apps.twitter.com/.
2. In the application's Settings tab, set the Website and Callback URL to https://api.backendless.com
3. From the Keys and Access Tokens tab, copy the Consumer Key and the Consumer Secret values.
4. Click the Permissions tab and select the Request email addresses from users checkbox. Click Update Settings.
5. Log in to your account in the Backendless Console, select your app and click Manage.
6. Scroll down to the Social Settings section and insert the values copied on step 4 into the Twitter Consumer key and Twitter Consumer secret fields.

The following methods should be used in the client application to perform login with a twitter account:

#define backendless [Backendless sharedInstance]

// blocking method:
- (void)[backendless.userService easyLoginWithTwitterFieldsMapping:
                                 (NSDictionary<NSString *,NSString *> *)fieldsMapping];
                                 
// non-blocking method:
- (void)[backendless.userService easyLoginWithTwitterFieldsMapping:
                                 (NSDictionary*lt;NSString *,NSString *> *)fieldsMapping 
                                 response:^(NSNumber *)responseBlock 
                                 error:^(Fault *)errorBlock];
let backendless = Backendless.sharedInstance()

// blocking method:
func backendless.userService.easyLogin(withTwitterFieldsMapping: [String : String]) -> Void

// non-blocking method:
Func backendless.userService.easyLogin(withTwitterFieldsMapping: [String : String], 
                             response: ((NSNumber) -> Void)responseBlock, 
                             error: ((Fault) -> Void)errorBlock)
Add the fields mapping with Twitter to allow the users to log in to your application with their Twitter account:

#define backendless [Backendless sharedInstance]

- (void)easyLoginWithTwitter {
    [backendless.userService
     easyLoginWithTwitterFieldsMapping:@{@"email":@"email"}
     response:^(NSNumber *result) {
         NSLog(@"login: (Twitter) result = %@", result);     }
     error:^(Fault *fault) {
         NSLog(@"Server reported an error: %@", fault.detail);
     }];
}
let backendless = Backendless.sharedInstance()

func loginWithTwitter () {
    backendless.userService.easyLogin(
        withTwitterFieldsMapping: ["email":"email"],
        response: {
            (result : NSNumber?) -> Void in
            print("login: (Twitter) result = \(result)")
    },
        error: {
            (fault : Fault?) -> Void in
            print("Server reported an error: \(fault.description)")
    })
}

 

 

Login with Google

This guide describes the process of configuring your Backendless backend to enable the Google account sign-in in your application. The result of the integration allows users with Google accounts to login to your app while being represented within the app as BackendlessUser.

 

Client side setup

1. Follow the "Google Sign-in with iOS" instructions from: https://developers.google.com/identity/sign-in/ios/start-integrating
 

Configuring Backend

2. Select the project and click Credentials.
3. The credentials should now include a "Web client" which is automatically generated by Google.
webclient
4. Click Web client.
5. Copy Client ID and Client secret into Google Plus ID and Google Plus Secret located in the Manage > App Settings > Social Settings in Backendless console:
client-id
 
google-client-id-v4

 

In order to complete Google Sign-In with Backendless, your application must use the following functions once a delegate for Google sign-in is invoked:

#define backendless [Backendless sharedInstance]

// blocking method:
- (BackendlessUser *)[backendless.userService loginWithGoogleSDK:(NSString *)idToken 
                                              accessToken:(NSString *)accessToken];
 
// non-blocking method:
- (void)[backendless.userService loginWithGoogleSDK:(NSString *)idToken 
                                              accessToken:(NSString *)accessToken 
                                              response:^(BackendlessUser *)responseBlock 
                                              error:^(Fault *)errorBlock];
let backendless = Backendless.sharedInstance()

// blocking method:
func backendless.userService.login(withGoogleSDK(YOUR-idToken): String, 
                                   accessToken: String) -> BackendlessUser
                                   
// non-blocking method:
func backendless.userService.login(withGoogleSDK(YOUR-idToken): String, 
                                   accessToken: String, 
                                   response:(BackendlessUser -> Void)responseBlock, 
                                   error: (Fault -> Void)errorBlock) -> Void

 

Example

AppDelegate.h
#import <Google/SignIn.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, GIDSignInDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
AppDelegate.m
#define backendless [Backendless sharedInstance]


- (BOOL)application:(UIApplication *)application 
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [backendless initApp:@"XXXXX" APIKey:@"XXXXX"];    

    // Initialize sign-in
    NSError* configureError;
    [[GGLContext sharedInstance] configureWithError: &configureError];
    NSAssert(!configureError, @"Error configuring Google services: %@", configureError);
    [GIDSignIn sharedInstance].delegate = self;
    return YES;
}


- (BOOL)application:(UIApplication *)app 
            openURL:(NSURL *)url 
            options:(NSDictionary *)options {
    return [[GIDSignIn sharedInstance] handleURL:url 
                               sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]                                     
                                      annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
}

- (void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error {
    if (!error) {
        // Perform backendless operations on signed in user here
        [backendless.userService loginWithGoogleSDK:user.authentication.idToken
                                        accessToken:user.authentication.accessToken
                                           response:^(BackendlessUser *user) {
                                               NSLog(@"User = %@", user);
                                           }
                                              error:^(Fault *fault) {
                                                  NSLog(@"Server reported an error: %@", fault.description);
                                              }
         ];        
    }
}

- (void)signIn:(GIDSignIn *)signIn didDisconnectWithUser:(GIDGoogleUser *)user withError:(NSError *)error {
    NSLog(@"User has disconnected.");    
}
ViewController.h
#import <Google/SignIn.h>

@interface ViewController : UIViewController<GIDSignInUIDelegate>
@end
ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [GIDSignIn sharedInstance].uiDelegate = self;
    
    GIDSignInButton *signInButton = [GIDSignInButton new];
    signInButton.center = self.view.center;
    [self.view addSubview:signInButton];
}

@end
AllDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate { 
let backendless = Backendless.sharedInstance()!


func application(_ application: UIApplication, 
                   didFinishLaunchingWithOptions 
                   launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    backendless.initApp("XXXXX", apiKey: "XXXXX")

    // Initialize sign-in
    var configureError: NSError?
    GGLContext.sharedInstance().configureWithError(&configureError)
    assert(configureError == nil, "Error configuring Google services: \(configureError)")
    GIDSignIn.sharedInstance().delegate = self
    return true
}


func application(_ app: UIApplication, 
                 open url: URL, 
                 options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        return GIDSignIn.sharedInstance().handle(url, 
        sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String,
        annotation: options[UIApplicationOpenURLOptionsKey.annotation])
}


func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
    // Perform backendless operations on signed in user here
    backendless.userService.login(withGoogleSDK: user.authentication.idToken,
                                  accessToken: user.authentication.accessToken,
                                  response: {
                                    (user : BackendlessUser?) -> Void in
                                    print("User = \(user)")
    },
                                  error: {
                                    (fault : Fault?) -> Void in
                                    print("Server reported an error: \(fault?.description)")
    })
}

func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!, withError error: Error!) {
    print("User has disconnected.")
}
ViewController.swift
class ViewController: UIViewController, GIDSignInUIDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        GIDSignIn.sharedInstance().uiDelegate = self
        
        let signInButton = GIDSignInButton()
        signInButton.center = view.center
        view.addSubview(signInButton)
    }
}

User Properties

When a user registers with an application, he provides information which establishes user's identity. For one application these properties may include name, phone number and email address, for another it could be user id, age and gender. Backendless User Service allows each application to have a custom set of properties associated with the user entity.

Defining properties with the API

User properties must be defined in the object representing a user. When user registers or user object is updated, Backendless saves the property values in the Users table of Backendless Data service. If a property is not declared on the server-side, Backendless automatically creates a definition for it. This behavior can be turned off/on using the Dynamic Schema Definition configuration setting in the console (select Data > Configuration). The setting is turned on by default:

dynamic-schema-definition.zoom70

 

Set/Update User Property

The following API can be used to set or update properties in a user object:

Methods to set/add user properties are available in the BackendlessUser class. When registering a new user, the methods below can be used on a new instance of BackendlessUser. When updating an existing user, the instance must be retrieved from the server (either via login or standard data object retrieval).

// sets a property. The key argument is the property name
-(void)setProperty:(NSString *)key object:(id)value;

// clears (removes) all user properties and replaces them with the specified collection
-(void)setProperties:(NSDictionary<NSString*, id> *)props

// updates all existing user properties with the ones from the specified collection
-(void)updateProperties:(NSDictionary<NSString*, id> *)props;

// adds new properties to the user object
-(void)addProperties:(NSDictionary<NSString*, id> *)props;
// sets a property. The key argument is the property name
setProperty(key: String, object: Any)

// clears (removes) all user properties and replaces them with the specified collection
setProperties(props: [String : Any])

// updates all existing user properties with the ones from the specified collection
func updateProperties(props: [String : Any])

// adds new properties to the user object
func addProperties(props: [String : Any])

Retrieve User Property Values

The following API can be used to get user property values:

// retrieves value of a property with the name identified by "key"
-(id)getProperty:(NSString *)key;

// retrieves all properties for the user object
-(NSDictionary<NSString*, id> *)getProperties;

// removes the property identified by the name
-(void)removeProperty:(NSString *)key;

// removes all properties identified by names
-(void)removeProperties:(NSArray<NSString*> *)keys;
// retrieves value of a property with the name identified by "key"
func getProperty(key: String) -> Any

// retrieves all properties for the user object
func getProperties() -> [String : Any]

// removes the property identified by the name
func removeProperty(key: String)

// removes all properties identified by names
func removeProperties(keys: [String])

Example:

The example below describes how to retrieve the phoneNumber user property.

-(void)loginUserAndGetProperties { 
     
    @try { 
         
        BackendlessUser *loggedUser = [backendless.userService login:@"spidey@backendless.com" password:@"greeng0blin"]; 
        NSLog(@"User has been logged in: %@", loggedUser); 
         
        BackendlessUser *user = backendless.userService.currentUser; 
        if (user) { 
            // get user's phone number (i.e. custom property) 
            NSString *phoneNumber = [user getProperty:@"phoneNumber"]; 
            NSLog(@"User email: %@, phoneNumber: %@", userEmail, phoneNumber); 
        } 
        else { 
            NSLog(@"User hasn't been logged"); 
        } 
    } 
     
    @catch (Fault *fault) { 
        NSLog(@"Server reported an error: %@", fault); 
    } 
} 
  func loginUserAndGetProperties() {
        
        Types.tryblock({ () -> Void in
            
            let loggedUser = self.backendless.userService.login("spidey@backendless.com", password: "greeng0blin")
            print("User has been logged in: \(loggedUser)")
            
            let user = self.backendless.userService.currentUser
            if (user != nil) {
                // get user's email (i.e. mandatory/predefined property)
                let userEmail = user.email
                // get user's phone number (i.e. custom property)
                let phoneNumber = user.getProperty("phoneNumber") as! String
                print("User email: \(userEmail), phoneNumber: \(phoneNumber)")
            }
            else {
                print("User hasn't been logged")
            }
            },
            
            catchblock: { (exception) -> Void in
                print("Server reported an error: \(exception as! Fault)")
            }
        )
    }

Defining properties with Console

User property management is available in the schema editor of the Users table. To open the schema editor:

1. Login to Backendless Console and select your application.
2. Click the Data icon, and select the Users table.
3. Click the Schema menu.

There are built-in user properties, such as email, name and password. The email and name properties can be deleted if they do not apply to your app, although it is recommended to keep the email property, since it is used for other purposes (for instance, password recovery). The password property cannot be deleted.

Identity Property

It is required that one of the properties is marked as identity. This is the property Backendless uses for the Login and Restore Password operations. As users register, Backendless ensures the value for the identity property is unique  in the context of a specific version of the application.

Password Property

"password" is a special property. Backendless automatically adds the property when an application is created. The following rules apply to the password property:

Password cannot be removed from the application.
Password cannot be used as identity.
Password is always a required property

users-schema.zoom80

To add a new property, click the New button.

Update User Object

User property values of the logged in users can be updated with an API operation described below. This operation is useful if an application needs to provide to the users the functionality for updating user profiles and registration properties. A user must be logged in in order to update his registration properties.

 

Non-blocking Methods:

#define backendless [Backendless sharedInstance]

-(void)[backendless.userService update:(BackendlessUser *)user 
                       response:(void(^)(BackendlessUser *))responseBlock 
                       error:(void(^)(Fault *))errorBlock];
let backendless = Backendless.sharedInstance()

func backendless.userService.update(user: BackendlessUser, 
                                    response: ((BackendlessUser?) -> Void)responseBlock, 
                                    error: ((Fault?) -> Void)errorBlock)

where

user - an instance of the BackendlessUser class which contains property values for the account registration.

Blocking Methods:

#define backendless [Backendless sharedInstance]

-(BackendlessUser *)[backendless.userService update:(BackendlessUser *)user];
let backendless = Backendless.sharedInstance()

func backendless.userService.update(user: BackendlessUser) -> BackendlessUser

where

user - an instance of the BackendlessUser class which contains property values to update the user account with.

The return value from the server is BackendlessUser object.

Errors:

The following errors may occur during the Update User Properties API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3018

The property marked as "identity" is being updated and another user already has the specified value which must be unique.

3024

General "update registration" error. Error message should contain additional details.

3028

User is not logged in.

3029

Cannot modify properties of another user. Returned when one user is logged and the call attempts to modify properties of another user.

3030

Unable to locate user account - invalid user id.

3031

A new "dynamic" property is being added, but dynamic property definition is disabled.

3045

Required properties in the provided object do not contain values.

 

Example:

#define backendless [Backendless sharedInstance]

-(void)updateUserProps {
    BackendlessUser *user = // obtain the BackendlessUser object here
    [user updateProperties:@{@"name" : @"James"}];
    [backendless.userService
     update:user
     response:^(BackendlessUser *updatedUser) {
         NSLog(@"User has been updated (ASYNC): %@", updatedUser);
     }
     error:^(Fault *fault) {
         NSLog(@"Server reported an error (ASYNC): %@", fault);
     }];
}
let backendless = Backendless.sharedInstance()

func updateUserProps () {
    let user = // obtain BackendlessUser object here
    user?.updateProperties(["name" : "James"])
    backendless.userService.update(user,
                                   response: {
                                    (updatedUser : BackendlessUser?) -> Void in
                                    print("User has been updated: \(updatedUser)")
    },
                                   error: {
                                    (fault : Fault?) -> Void in
                                    print("Server reported an error: \(fault?.description)")
    })
}

Get Current User

An iOS application can retrieve an instance of BackendlessUser representing the currently logged in user using the read-only property currentUser:

#define backendless [Backendless sharedInstance]
BackendlessUser *currentUser = backendless.userService.currentUser;
let backendless = Backendless.sharedInstance()!
let currentUser : BackendlessUser = backendless.userService.currentUser

If a user is not logged in, the method returns nil.

Logout

The Logout operation terminates user session and disassociates the AuthenticatedUser role from the subsequent requests made by the client application.

 

Asynchronous Method Signatures:

#define backendless [Backendless sharedInstance]

-(void)[backendless.userService logout:(void(^)(id))responseBlock 
                                 error:(void(^)(Fault *))errorBlock];
let backendless = Backendless.sharedInstance()

func backendless.userService.logout(responseBlock: ((Any) -> Void)responseBlock, 
                                    error: ((Fault?) -> Void))errorBlock) -> Void

Synchronous Method Signatures:

#define backendless [Backendless sharedInstance]

-(id)[backendless.userService logout];
let backendless = Backendless.sharedInstance()

func backendless.userService.logout() -> Any

Errors:

The following errors may occur during the Logout API call.See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3007

Invalid application-id or version.

3023

General error while executing logout. Error details should be available in the message property.

Example:

#define backendless [Backendless sharedInstance]

[backendless.userService logout:^(id result) {
    NSLog(@"User has been logged out");
  }
  error:^(Fault *fault) {
    NSLog(@"Server reported an error: %@)", fault.description);
  }];}
let backendless = Backendless.sharedInstance()!

backendless.userService.logout({
    (result : Any?) -> Void in
    print("User has been logged out")
  },
  error: {
    (fault : Fault?) -> Void in
    print("Server reported an error: \(fault?.description)")
  })

Password Reset

Backendless supports two kinds of password reset:

1. Reset to system (backend) generated password.
2. Reset to password assigned by the user.

With the first option, an email is sent to the user with a regenerated password assigned to the user account. With the second option, an email is sent with a link to a webpage which contains a form where the user can enter the new password.

You can choose the type of reset for your application using Backendless Console. To change the password reset approach navigate to Users > Email Templates and select either User requests password recovery or User requests password recovery by link. Only one of these options can be active at a time. To make an option active, make sure the Do not send email for the event checkbox is not selected. If you make a change, make sure to click the SAVE button at the bottom of the screen.

 

For example, the following screenshot shows a configuration where the system will send out a link so the user can assign his own password. Notice the red color for the User requests password recovery option - this indicated that the option is disabled for the app:

active-mail-template.zoom80

 

In both cases (system-assigned or user-assigned passwords), the password reset sequence must initiated through the API. When the system assigns a password, it is still possible to update the user password using the Data Service API.

 

For the "password change via a link" option Backendless provides a default HTML form the link in the email points to. The HTML page is located in the File storage of the application where it can be edited and customized for branding purposes. The files are located in the /web/templates/change_password directory:

password-change-template.zoom80

Asynchronous Method Signatures:

#define backendless [Backendless sharedInstance]

-(void)[backendless.userService restorePassword:(NSString *)login 
                                                response:(void(^)(id))responseBlock 
                                                error:(void(^)(Fault *))errorBlock];
let backendless = Backendless.sharedInstance()

backendless.userService.restorePassword(login: String, 
                                        response: ((Any?) -> Void)responseBlock, 
                                        error: ((Fault?) -> Void)errorBlock)

where

login     - a value for the property marked as identity which uniquely identifies the user within the application.

Synchronous Call Method Signatures:

#define backendless [Backendless sharedInstance]

-(id)[backendless.userService restorePassword:(NSString *)login];
let backendless = Backendless.sharedInstance()

func backendless.userService.restorePassword(login : String) -> Any

where

login   - value for a property marked as identity which uniquely identifies the user within the application.

Errors:

The following errors may occur during the Password Recovery API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3020

Unable to find user with the specified login (invalid user identity).

3025

General password recovery error. Additional details should be available in the "message" property of the response.

3038

One of the requirement arguments (application id, version or user identity) is missing.

 

Example:

#define backendless [Backendless sharedInstance]

[backendless.userService restorePassword:@"james.bond@mi6.co.uk"
                             response:^(id result) {
                               NSLog(@"Please check your email inbox to reset your password");
                             }
                             error:^(Fault *fault) {
                               NSLog(@"Server reported an error: %@)", fault.description);
                             }];
let backendless = Backendless.sharedInstance()

backendless.userService.restorePassword("james.bond@mi6.co.uk",
               response: {
                 (result : Any) -> Void in
                 print("Please check your email inbox to reset your password")
               },
               error: {
                 (fault : Fault?) -> Void in
                 print("Server reported an error: \(fault?.description)")
               })

User to Geo Relations

Backendless Geo Service manages application's geo location data and provides APIs to work with geopoints. Backendless supports integration between user objects managed by User Service and geopoints for the scenarios when a logical connection between the two entity types must exist in an application.

 

Relations between a user object and a geopoint are built the same way as between a data object and a geo point. The User-to-Geo integration is implemented through object relations. The Users table schema may declare a table column with a special data type - "GeoPoint Relationship". As a result, the user objects in the table may contain a reference to one or more geopoints. Backendless provides an API to establish a relationship between a user object and geopoints. Additionally, when a user object is retrieved by using the data retrieval API, any related geopoints can be retrieved by using the same mechanisms used for loading data relations. The user-to-geo relation is bidirectional, it means a geopoint may reference a user object in its metadata. You can learn more about it in the Relations with Data Objects section of the Geolocation service documentation.

 

A relation between a user object and a geopoint (or a collection of) can be established by using either the "code first" or the "schema first" approaches. With the former, the relationship is established through an API call. If the relationship is not declared in the schema, Backendless creates it based on the information in the API. With the "schema first" approach, application developer can declare a user-to-geo property first. In either one of these approaches, once a relationship is declared, user objects and geopoints may be linked together by using the Backendless console as well. For information on how to declare Users to Geo relation columns and how to link User objects with GeoPoints using Backendless console, see the Relations with Geo Objects of the Data Service documentation.

 

To create a relation both the user object and geopoint(s) must already exist in the Backendless database. It is not possible to create a relation with an object which has not been previously saved.

Creating User-to-Geo Relations with the API

Creating a relationship between a user object and a geopoint (or a collection of) uses the same API as setting/adding related objects to a data object. In the case of the user-to-geo relations, the related entity is a geopoint or a collection of geopoints.

The example below demonstrates how to assign coordinates to a user's location, that is link a user object with one or several geopoints.

One-to-One Relation

id<IDataStore>dataStore = [backendless.data ofTable:@"Users"];
    
// Create a geopoint first
GeoPoint *geoPoint = [GeoPoint geoPoint:(GEO_POINT){.latitude = 48.85, .longitude = 2.35}];
[backendless.geoService savePoint:geoPoint
               response:^(GeoPoint *savedGeoPoint) {
                 NSLog(@"GeoPoint saved: %@", savedGeoPoint);
               }
               error:^(Fault *fault) {
                 NSLog(@"Server reported an error: %@", fault);
               }
];

// Link the geopoint with the user. Do not call geopoint creation 
// and linking to the user back-to-back. These two calls are
// asynchronous. Establishing a relation between user and geopoint
// must be done only after geopoint has been created
BackendlessUser *user = // obtain a reference to user here    
[dataStore setRelation:@"location:GeoPoint:1"
        parentObjectId:user.objectId
          childObjects:@[@"geopoint.objectId"]
              response:^(NSNumber *result) {
                NSLog(@"Relation has been saved: %@", result);
              }
                 error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault);
                 }
];
let dataStore = backendless.data.ofTable("Users")
let geoPoint = GeoPoint.geoPoint(GEO_POINT(latitude:48.85, longitude: 2.35)) as! GeoPoint
backendless.geoService.save(geoPoint,
                response: {
                  (savedGeoPoint : GeoPoint?) -> () in
                  print("GeoPoint saved: \(savedGeoPoint)")
                },
                error: {
                  (fault : Fault?) -> () in
                  print("Server reported an error: \(fault)")
                })


let user : BackendlessUser = // obtain user object here

// Link the geopoint with the user. Do not call geopoint creation 
// and linking to the user back-to-back. These two calls are
// asynchronous. Establishing a relation between user and geopoint
// must be done only after geopoint has been created
dataStore.setRelation("categoryName:GeoPoint:1",
                parentObjectId: user.objectId as String!,
                childObjects: ["geopoint.objectId"],
                response: {
                  (result : NSNumber?) -> Void in
                  print("Relation saved :\(result)")
                },
                error: {
                  (fault : Fault?) -> Void in
                  print("Server reorted an error: \(fault?.description)")
                })

Backendless Console can be used to verify the new relation. The geopoint will show up as a property in the location column of the user object.

One-to-Many Relation

// IMPORTANT: the three API calls shown below are asynchronous (non-blocking).
// This means you should not call them back-to-back as the result of geopoint
// creation is used in the setRelation API. Make sure to call setRelation
// only after geopoints have been saved in Backendless. 

id<IDataStore>dataStore = [backendless.data ofTable:@"Users"];
BackendlessUser *user = // obtain user object here
    
GeoPoint *geoPoint1 = [GeoPoint geoPoint:(GEO_POINT){.latitude = 48.85, .longitude = 2.35}];
[backendless.geoService savePoint:geoPoint1
                response:^(GeoPoint *savedGeoPoint) {
                  NSLog(@"GeoPoint saved: %@", savedGeoPoint);
                }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }
];
    
GeoPoint *geoPoint2 = [GeoPoint geoPoint:(GEO_POINT){.latitude = 40.40, .longitude = 3.68}];
[backendless.geoService savePoint:geoPoint2
                response:^(GeoPoint *savedGeoPoint) {
                  NSLog(@"GeoPoint saved: %@", savedGeoPoint);
                }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }
];
    
[dataStore setRelation:@"locations:GeoPoint:n"
        parentObjectId:user.objectId
          childObjects:@[@"geopoint1.objectId", @"geopoint2.objectId"]
              response:^(NSNumber *result) {
                NSLog(@"Relations have been saved: %@", result);
              }
                 error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault);
                 }
];
// IMPORTANT: the three API calls shown below are asynchronous (non-blocking).
// This means you should not call them back-to-back as the result of geopoint
// creation is used in the setRelation API. Make sure to call setRelation
// only after geopoints have been saved in Backendless. 

let dataStore = backendless.data.ofTable("Users")
let user : BackendlessUser = // obtain user object here

let geoPoint1 = GeoPoint.geoPoint(GEO_POINT(latitude:48.85, longitude: 2.35)) as! GeoPoint
backendless.geoService.save(geoPoint1,
                            response: {
                                (savedGeoPoint : GeoPoint?) -> () in
                                print("GeoPoint saved: \(savedGeoPoint)")
                            },
                            error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
                            })

let geoPoint2 = GeoPoint.geoPoint(GEO_POINT(latitude:40.40, longitude: 3.68)) as! GeoPoint
backendless.geoService.save(geoPoint2,
                            response: {
                                (savedGeoPoint : GeoPoint?) -> () in
                                print("GeoPoint saved: \(savedGeoPoint)")
                            },
                            error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
                            })


dataStore?.setRelation("categoryName:GeoPoint:n",
                       parentObjectId: user.objectId as String!,
                       childObjects: ["geopoint1.objectId", "geopoint2.objectId"],
                       response: {
                        (result : NSNumber?) -> Void in
                        print("Relation saved :\(result)")
                       },
                       error: {
                        (fault : Fault?) -> Void in
                        print("Server reorted an error: \(fault?.description)")
                      })

Backendless Console can be used to verify the new relation. The geopoint will show up as a property in the locations column of the user object.

Security and User Roles

Application's user accounts can be mapped to roles using the API documented below. A Backendless backend contains some system roles and may include developer-defined roles. There several system roles available in all Backendless backends. These roles are automatically assigned to every user under various conditions. Each role can configured with global, container and asset permissions.

 

Core security roles are assigned based on user authentication status and whether the user logged in via "classic" backendless login or through a social network. These roles are:

Security Role

Assigned to user when an API request is made by:

NotAuthenticatedUser

...a user who is not logged in.

AuthenticatedUser

...a user who is logged in.

SocialUser

...a user who is logged in via a social network (Facebook, Twitter, Google)

FacebookUser

...a user who is logged in via Facebook

GooglePlusUser

...a user who is logged in via Google

TwitterUser

...a user who is logged in via Twitter

 

Additionally the following roles are automatically assigned by Backendless to an API request when a corresponding secret API key is used. By configuring access permissions to these roles an application may allow to custom server-side code (ServerCodeUser role), but completely reject access to the client apps (all other roles).

Security Role

Role is assigned when API uses secret key:

ASUser

ActionScript secret key

AndroidUser

Android secret key

DotNetUser

.NET secret key

IOSUser

iOS secret key

JSUser

JavaScript secret key

RestUser

REST secret key

ServerCodeUser

CodeRunner secret key

 

Backendless assigns multiple security roles for the same API request. For example, an API request made from a JavaScript application where user is not logged will have the following roles assigned: NotAuthenticatedUser and JSUser.

 

Adding Roles

A developer-defined role can be added using Backendless Console:

1. Login to Console and select an application.
2. Click the Users icon.
3. Click the Security and Permissions section.
4. Click the Add Role button and enter a name for the role.
5. Click OK to save the role.

Role Permissions

A role may have multiple permissions assigned to it. A permission may either allow (GRANT) or reject (DENY) an API operation. Every operation which can be restricted with a permission has a subject. For example, the subject of a Data Service operation is a data record. For the File Service operations, the subject is a file.

All permissions are organized into a hierarchy: The global permissions apply to everything in the system. They either allow (GRANT) or reject (DENY) an API operation without distinguishing the subject. For example, a global permission may reject the Data Service's find operations for the users in the NotAuthenticatedRole. As a result, any not-authenticated user will not be able to retrieve an object from any data table in the application.

The asset container permissions inherit from the global ones and apply to a specific asset container, such as a data table. These permissions are scoped to a specific container type. For example, you may reject the get operation on file directory /privatefiles/ for the users in role X. As a result, any user who belongs to the role X will not be able to retrieve files from that directory.

Finally, the asset permissions inherit from the ones for the corresponding asset container (if they are available) or from the global set. For example, an asset permission may reject access to a specific data object for one role and grant access to the same object for another role.

The diagram below illustrates the permissions inheritance ladder:

permission-inheritance

Once a role is assigned to a user account, any permissions assigned to the role (either granting or denying an API operation) automatically apply to the operations executed by the application on behalf of the user.

Retrieving Available User Roles

This operation returns a list of the roles associated with the user account currently logged in to the Backendless application. If the request is sent without logging in, the system will return only one role - NotAuthenticatedUser.

 

#define backendless [Backendless sharedInstance]

// Synchronous method with fault thrown as an exception:
- (NSArray<NSString *> *)[backendless.userService getUserRoles];

// Asynchronous method with block-based callbacks:
-(void)[backendless.userService getUserRoles:(void(^)(NSArray *))responseBlock 
                                             error:(void(^)(Fault *))errorBlock];
let backendless = Backendless.sharedInstance()

// Synchronous method with fault thrown as an exception:
func backendless.userService.getUserRoles() -> [String]

// Asynchronous method with block-based callbacks:
func backendless.userService.getUserRoles(responseBlock: (([String]) -> Void)responseBlock, 
                                          error: ((Fault?) -> Void)errorBlock) -> Void            

Example:

#define backendless [Backendless sharedInstance]

- (void)getUserRoles {
    [self initBackendless];
    [backendless.userService login:@"james.bond@mi6.co.uk" password:@"iAmWatchingU"];
    
    [backendless.userService getUserRoles:^(NSArray<NSString *> *userRoles) {
        for (NSString *role in userRoles) {
            NSLog(@"role: %@", role);
        }
      }
      error:^(Fault *fault) {
        NSLog(@"Server reported an error: ", fault.description);
      }];
}
let backendless = Backendless.sharedInstance()!

func getUserRoles() {
    backendless.userService.login("james.bond@mi6.co.uk", password: "iAmWatchingU")
    backendless.userService.getUserRoles({
        (userRoles : [String]?) -> Void in
        for role in userRoles! {
            print("role: \(role)")
        }
      },
      error: {
        (fault : Fault?) -> Void in
        print("Server reported an error: \(fault?.description)")
      })
}

Assigning a Role to a User:

This operation is available only from Custom Business Logic. Applications must use the "Code Runner API key" in order for the API call to be  accepted on the server. The reason for this restriction is that a malicious use of this API can easily compromise application's security. As a result, this API must be used from a controlled environment.

 

Unassigning a Role from a User:

This operation is available only from Custom Business Logic. Applications must use the "Code Runner API key" in order for the API call to be  accepted on the server. The reason for this restriction is that a malicious use of this API can easily compromise application's security. As a result, this API must be used from a controlled environment.

Errors:

The following errors may occur during the API calls described above. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

2005

Could not find role.

3038

One of the required parameters (user identity or roleName) is null.

3057

Could not find user by id or identity.

3058

Could not assign role to user.

3059

Could not unassign role to user.

Global Permissions

The global service permissions apply to all operations of a Backendless service. For example, global Data Service permissions assigned to a role apply to all API operations without distinguishing the subject of an operation. (A subject is a resource targeted by an API operation - for example a data object in a "save" call).

Global permissions can be viewed and managed in Backendless Console.  After selecting an application, click Users, then Security Roles:

user-rolesv4.zoom70

 

Every Backendless service is represented by a column. Each cell in a column is a group of API operations. For example "Find" under "Data" represents all Find operations of the Data Service. The check mark in a cell indicates that that the operation is globally permitted. Each global permission has two states:

Grant - represented by a green check mark - grants the rights to execute an operation for the users in the selected role.
Deny - represented by a red X - denies the rights to execute an operation.

Asset Container Permissions

Asset Container permissions apply to all subjects within a specific container. Supported containers may be:

Data tables                - a permission assigned at that level applies to all operations on the table. For example, storing, deleting or updating objects in the table and running queries.
Messaging channels        - a permission assigned to a channel applies to all operations on the channel - publishing a message, subscribing to receive messages, etc.
File directories        - a permission assigned to a file directory applies to all file-related operations in the directory - file upload, fetching a file, etc.
Geo categories        - a permission assigned to a geo category applies to all geo point-related operation in the category - adding, deleting or updating a geo point
Media tubes        - a permission assigned to a media tube applies to all operation on media streams in the tube.

To view, assign or modify an asset container permission, use a corresponding screen in the Backendless Console. For example, to restrict access to a data table, switch to the Data view, select a table and click the Permissions menu. The user interface has two views - one is for managing user permissions and the other for roles. To modify permissions for a user account:

1. Click the User Permissions menu.
2. Enter the user name in the search field to locate the user account..
3. Click an icon representing the permission state to modify the permission.

user-permissionsv4.zoom70

Similarly permissions can be assigned or modified for specific roles - use the Role Permissions menu.

Asset Permissions

Asset permissions are the most granular in the Backendless security system as they apply to a specific object. Currently this type of permissions is available only for data objects stored in Backendless Data Service and files stored in Backendless File Service. Permissions assigned to an asset (data objects or files) supersede all other permissions - including global and asset container ones. Similar to the asset container permissions, asset permissions may apply to specific user accounts or roles. This approach becomes particularly useful when different objects/files in the same container (table or directory) should have different access levels. For example, suppose a data table called Orders contains purchase orders entered by different sales people. Every sales person should be able to see only their purchase orders, but the sales manager should see the purchase orders of his subordinates. This can be accomplished by using an asset permission scheme.

Data Service Asset Permissions

Permissions for individual data objects can be managed either with Backendless Console or using the API. To manage object's permissions with console:

1. Login to console and select an application.
2. Click Data, then select the table containing the object for which permissions should be managed.
3. There is a special "lock" icon in the ACL column for every object in the table.
acl-iconv4.zoom80
4. Click the lock icon to manage object's permissions.
5. The permissions user interface includes two menu items: Users Permissions and Roles Permissions. The former is responsible for assigning permissions for the selected object to specific users. The latter does the same but for application roles.

The screenshot below explains various parts of the user interface:

roles-permissions-v4.zoom80

The intersection of each column and a role/user either grants or denies access to the operations represented by the column. The intersections may contain the following visualizations:

ico-checkmark-disable (gray check mark)        - INHERIT. This state represents the permissions for the user/role and the operation are inherited from either asset container or global permissions.
ico-checkmark2 (green check mark) - GRANT. This state represents that the permission is granted for the user/role to perform the corresponding operation.
ico-error (red cross)                - DENY. This state represents that the permission to execute the corresponding operation is denied to the user/role.

 

Data Service API

Overview

The Backendless Data Service is a highly scalable object storage system. The system has the qualities of both a traditional structured/relational (SQL) storage and an unstructured NoSQL database. Objects are stored in tables, which have columns with assigned data types. A table can be created dynamically, when an object is being persisted and there is no table corresponding to the object's type. Similarly, when an object is saved or updated  and contains properties which do not have corresponding columns, Backendless dynamically creates the columns in the table.

 

There are two formats for representing data objects: using custom classes or using dictionaries. With the custom class approach all properties of a class are mapped to data table columns. Instances of class are saved in a data table with the same name as the name of the class. When using dictionaries, objects are structured as key/value pairs where keys identify column names and values their respective values. See the Data Object section for additional details.

 

Data Service supports table relations which means a table may declare a column referencing another table. At the object level it is implemented through an object property which contains a reference to another object (one to one relation) or a collection of objects (one to many relations). When an object is retrieved from the server, its related objects can be retrieved in the same request (single-step retrieval) or later on through a separate request (two-step retrieval).

 

Object search in Backendless can be done using the SQL syntax. A search request is sent to a specific table and a search query identifies a condition the found object must match. As a result, the query must be the format of what goes into the WHERE clause of a traditional SQL query. For more details see the Search with the Where clause section of the guide.

Data Object

Backendless persistence system stores objects in data tables. A persisted object is stored in a language-independent format. For iOS applications, there are two ways to represent data objects on the client side:

 

1. Data objects can be stored/retrieved as instances of NSDictionary for Obj-C apps or object maps [String:AnyObject] for Swift apps. For example, the following code saves an object with properties "name" and "age" in the "Person" table:
// create a data object with NSDictionary
NSDictionary *person = @{
                   @"name": @"Joe",
                   @"age": @(25) };

// Now save it on the server:
[[backendless.data ofTable:@"Person"] save:person
   response:^(NSDictionary<NSString*,id> *result) {
      NSLog(@"Person has been saved (ASYNC): %@", result);
   }
   error:^(Fault *fault) {
      NSLog(@"Server reported an error (ASYNC): %@", fault);
   }];
// create a data object using an object map
let person = [
    "name": "Joe",
    "age": 25
    ] as [String : Any]

// Now save the object on the server
Backendless.sharedInstance().data.ofTable( "Person" ).save( person,
   response: { 
     (result : [String:Any]?) -> Void in
     print("\(result)")
   },
   error: { 
     (fault : Fault?) -> () in
     print("Server reported an error: \(fault)")
   })

or
 
2. Data objects can be represented as instances of custom classes which correspond to the data tables. Object's class does not need to implement any special Backendless interfaces or extend a class from the Backendless SDK. For example, the following code saves an object with properties "name" and "age" in the "Person" table:

// Person class declaration:
@interface Person : NSObject
 @property (strong, nonatomic) NSString *name;
 @property (strong, nonatomic) NSNumber *age;
@end

@implementation Person
@end

// Create an instance of Person using the interface/implementation above
Person *person = [Person new];
person.name = @"Joe";
person.age = @(25);

// Now save the object in the Backendless database
[[backendless.data of:Person.class] save:person
  response:^(id result) {
    NSLog(@"Person has been saved (ASYNC): %@", result);
  }
  error:^(Fault *fault) {
    NSLog(@"Server reported an error (ASYNC): %@", fault);
  }];
// declare a class - make sure it is declare in a separate .swift file
class Person : NSObject {
  var name : String?
  var age : Int = 0
}

// Create a data object with the class declared above
let person = Person()
person.name = "Joe"
person.age = 25

// Now save the object in the database
Backendless.sharedInstance().data.of(Person.self).save(
    person,
    response: { (result : (Any?)) -> Void  in
        let person = result as! Person;
        print("\(person.name)")
        print("\(result)")
    },
    error: { ( fault : Fault?) -> () in
        print("Server reported an error: \(fault)")
    });

There are however a few requirements imposed on the classes of the persisted objects:

Class implementation must be kept outside of ViewController or AppDelegate classes. It is recommended to keep an implementation of the data object class in a separate source file.
Class must contain the default, publicly accessible no-argument constructor.
Class must contain either at least one property.
Optional requirement. Backendless automatically assigns a unique ID to every persisted object. If the application needs to have access to the assigned ID, the interface must declare the following property:
@property (nonatomic, strong) NSString *objectId;
var objectId: String?
Optional requirement. In addition to objectId, Backendless maintains two additional properties for every persisted object - created and updated. The former contains the timestamp when the object was initially created in the Backendless data store. The latter is updated every time the object is updated. To get access to these properties, the class must implement the following methods:
@property (nonatomic, strong) NSDate *created;
@property (nonatomic, strong) NSDate *updated;
var created: NSDate?
var updated: NSDate?
For Swift data objects, the Bool, Int, Double and Float types must not be declared optional:
// not applicable to Objective-c
import Foundation

class SampleDataObject : NSObject {

    var objectId : String?
    var counter: Int = 0
    var isPublished: Bool = true
}

 

Saving Data Objects

The API to save an object can be used for two separate scenarios: if an object has been previously saved, it is updated in the data store, otherwise it is saved (created). The save operation checks if the object has objectId assigned by the server. in that case, the object is updated, otherwise it is created in the Backendless data store. In case when there is no table for the persisted object, Backendless creates one and maps table's columns to the object's properties.

The objectId property is automatically assigned to all persisted objects when they are initially saved. See the Data Object section for details on objectId.

The Data Service API for saving an object is available via the Backendless singleton object:

// The singleton reference is defined in Backendless.h
#define backendless [Backendless sharedInstance]
// Obtaining the backendless singleton object
let backendless = Backendless.sharedInstance()

Your application objects are stored in Backendless tables. Objects can be of custom classes defined in your application or represented as instances of NSDictionary in Objective-C or object maps with the type of [String:AnyObject] in Swift. With the custom classes approach, instances of class A are stored in table "A" - Backendless creates tables automatically, however, if you prefer, you can create your tables and define the schema using Backendless console.

Each data table can be accessed using a 'data store' object - by using the IDataStore protocol. A reference to a data store can be obtained using the following code:

// obtaining IDataStore for the custom-class approach:
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// obtaining IDataStore for the dictionary-based approach:
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
// obtaining IDataStore for the custom-class approach:
let dataStore = backendless.data of(YOUR-CLASS().ofClass());

// obtaining IDataStore for the map-based approach:
let dataStore = backendless.data.ofTable("TABLE-NAME");

where

YOUR-CLASS - identifies a table on the server.
"TABLE-NAME" - is the name of the table for which to get the data store. All subsequent data store operations will be performed in the specified table.

All Data Service methods in Backendless must be accessed through IDataStore.

 

A data store object provides the following APIs for saving data objects in Backendless:

Synchronous Method:

// stores new object in Backendless and returns created object. If request 
// results in an error, it is thrown as an exception
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
id savedEntity = [dataStore save:(id)entity];
// Stores new object in Backendless and returns created object
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
let savedEntity = dataStore.save(entity : Any)

Asynchronous Method:

// stores new object in Backendless. Server's response (result or error) is 
// delivered through the block-based callbacks
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore save:(id)entity  
            response:(void(^)(id *))responseBlock 
            error:(void(^)(Fault *))errorBlock]
// Stores new object in Backendless 
// Server's response (result or error) is delivered through the block-based callbacks
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
dataStore.save(entity : Any,
              response:((Any) -> Void)responseBlock,
              error:((Fault) -> Void)errorBlock) -> Void

Synchronous Method:

// Stores new object in Backendless and returns created object
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSDictionary<NSString*, id> *savedEntity;
savedEntity = [dataStore save:(NSDictionary<NSString*,  id> *)entity];
// Stores new object in Backendless and returns created object
let dataStore = backendless.data.ofTable("TABLE-NAME")
[String : Any] savedEntity = dataStore.save(entity : [String : Any])

Asynchronous Method:

// Stores new object in Backendless
// Server's response (result or error) is delivered through the block-based callbacks
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
[dataStore save:(NSDictionary<NSString*, id> *)entity
       response:^(void(^)(NSDictionary<NSString*,  id> *))responseBlock,
       error:^(void(^)(Fault *))errorBlock];
// Stores new object in Backendless. 
// Server's response (result or error) is delivered through the block-based callbacks
let dataStore = backendless.data.ofTable("TABLE-NAME")
dataStore.save(entity : [String : Any],
               response:(([String : Any]) -> Void)responseBlock,
               error:((Fault) -> Void))errorBlock) -> Void

where:

entity - Object to persist
responseBlock - a block to handle successful result of an asynchronous call.
errorBlock - a block to handle fault result of an asynchronous call.

Return Value:

The synchronous method returns the saved object. The asynchronous call receives the return value through a callback executed through the block-callback.

Example:

Consider the following class which will be used in the example. The class will be used to create data objects persisted in Backendless:

@interface Contact : NSObject
@property (nonatomic, strong) NSString *objectId;
@property int age;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *phone;
@end
class Contact : NSObject {
    var objectId : String?
    var age : Int = 0
    var title : String?
    var name : String?
    var phone : String?   
}
The following code saves an instance of the Contact class in Backendless:

- (void)saveNewContactAsync {
    Contact *contact = [Contact new];
    contact.age = 33;
    contact.title = @"Coding Chief";
    contact.name = @"Dude";
    contact.phone = @"555-111-111";
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore save:contact
      response:^(Contact *contact) {
        NSLog(@"Contact saved");
      }
      error:^(Fault *fault) {
        NSLog(@"Server reported an error: %@", fault);
      }
    ];
}
func saveContact () {
  let contact = Contact()
  contact.age = 33;
  contact.title = "Coding Chief"
  contact.name = "Dude"
  contact.phone = "555-333-333"
  let dataStore = self.backendless.data.of(Contact().ofClass())        
  dataStore!.save(contact,
    response: {
      (contact) -> () in
      print("Contact saved")
    },
    error: {
      (fault : Fault?) -> () in
      print("Server reported an error: \(fault)")
    })
}
With the dictionary/map-driven approach, objects sent to the backend are represented as plain NSDictionary (Obj-C) or untyped object maps (Swift):

- (void)saveNewContactAsync {
    NSDictionary *contact = @{
                              @"age": @(33),
                              @"title": @"Coding Chief",
                              @"name": @"Dude",
                              @"phone": @"555-111-111"
                              };    
    id<IDataStore> dataStore = [backendless.data ofTable:@"Contact"];
    [dataStore save:contact
      response:^(NSDictionary<NSString*, id> *contact) {
        NSLog(@"Contact saved");
      }
      error:^(Fault *fault) {
        NSLog(@"Server reported an error: %@", fault);
      }
    ];
}
func saveMapContact () {
  let contact = ["age": 44,
                 "title": "Coding Chief",
                 "name": "Dude",
                 "phone": "555-444-444"] as [String : Any]
  let dataStore = self.backendless.data.ofTable("Contact")
  dataStore!.save(contact,
                  response: {
                    (contact) -> () in
                    print("Contact saved")
                  },
                  error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
                  })
}

 

 

Updating Data Objects

Updating a data object which has been previously stored in the Backendless data store can be done using the same API as for saving the object initially. Once an object has objectId assigned, the call to save the object will update it on the server.

The Data Service API for updating an existing object is available via the Backendless singleton object:

// The singleton reference is defined in Backendless.h
#define backendless [Backendless sharedInstance]
// Obtaining the backendless singleton object
let backendless = Backendless.sharedInstance()

Updating an object in a data table must be done with a 'data store' object - by using the IDataStore protocol. A reference to a data store can be obtained using the following code:

// Obtaining IDataStore for the custom-class approach
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// Obtaining IDataStore for the dictionary-based approach
id<IDataStore> dataStore = [backendless.data ofTable:@TABLE-NAME"];
// Obtaining IDataStore for the custom-class approach
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// Obtaining IDataStore for the dictionary-based approach
let dataStore = backendless.data.ofTable("TABLE-NAME")

where

YOUR-CLASS - identifies a table on the server.
"TABLE-NAME" - the name of the table for which to get the data store. All subsequent data store operations will be performed in the specified table.

All Data Service methods in Backendless must be accessed through IDataStore.

 

A data store object provides the following APIs for updating data objects in Backendless:

Synchronous Method:

// Updates an existing object in Backendless and returns updated object
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
id updatedEntity = [dataStore save:(id)entity];
// Updates an existing object in Backendless and returns updated object
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
let updatedEntity = dataStore.save(entity : Any)

Asynchronous Method:

// Updates an existing object in Backendless
// Server's response (result or error) is delivered through the block-based callbacks
// Successful result is the updated object
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore save:(id)entity
       response:(void(^)(id))responseBlock,
       error:(void(^)(Fault *))errorBlock];
// Updates an existing object in Backendless
// Server's response (result or error) is delivered through the block-based callbacks
// Successful result is the updated object
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
dataStore.save(entity : Any,
              response:((Any) -> Void)responseBlock,
              error:((Fault) -> Void)errorBlock) -> Void

Synchronous Method:

// Updates an existing object in Backendless and returns updated object
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSDictionary<NSString*, id> *updatedEntity;
updatedEntity = [dataStore save:(NSDictionary<NSString*, id> *)entity];
// Updates an existing object in Backendless and returns updated object
let dataStore = backendless.data.ofTable("TABLE-NAME")
updatedEntity = dataStore.save(entity: [String : Any]));

Asynchronous Method:

// Updates an existing object in Backendless
// Server's response (result or error) is delivered through the block-based callbacks
// Successful response returns updated object
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
[dataStore save:(NSDictionary<NSString*, id> *)objectToUpdate
       response:(void(^)(NSDictionary<NSString*, id> *))responseBlock,
       error:(void(^)(Fault *))errorBlock];
// Updates an existing object in Backendless
// Server's response (result or error) is delivered through the block-based callbacks
// Successful response returns updated object
let dataStore = backendless.data.ofTable("TABLE-NAME")
dataStore.save(entity: [String : Any],
               response:(([String : Any]) -> Void)responseBlock,
               error:((Fault) -> Void)errorBlock) -> Void

where:

objectToUpdate - Object to update in the database.
responseBlock - a block to handle successful result of an asynchronous call.
errorBlock - a block to handle fault result of an asynchronous call.

Return Value:

The synchronous method returns the updated object. The asynchronous call receives the return value (the updated object) through a callback executed through the block-callback

Example:

Consider the following class which will be used in the example. The class will be used to create data objects persisted in Backendless:

@interface Contact : NSObject
@property (nonatomic, strong) NSString *objectId;
@property int age;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *phone;
@end
class Contact : NSObject {
    var objectId : String?
    var age : Int = 0
    var title : String?
    var name : String?
    var phone : String?   
}

The following code saves an instance of the Contact class in Backendless:

- (void)updateContact:(Contact *)existingContact {
    [existingContact setAge:34];
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore save:existingContact
           response:^(Contact *contact) {
               NSLog(@"Contact updated");
           }
           error:^(Fault *fault) {
               NSLog(@"Server reported an error: %@", fault);
           }
     ];
}
func updateContact (existingContact : Contact) {
  let dataStore = self.backendless.data.of(Contact().ofClass())
  existingContact.age = 33;
  dataStore!.save(existingContact,
                  response: {
                    (updatedContact) -> () in
                    print("Contact saved")
                  },
                  error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
                  })
}
With the dictionary/map-driven approach, objects sent to the backend are represented as plain NSDictionary (Obj-C) or untyped object maps (Swift):

- (void)updateMapContact:(NSDictionary<NSString*, id> *)existingContact {
    [existingContact setValue:@"34" forKey:@"age"];
    id<IDataStore> dataStore = [backendless.data ofTable:@"Contact"];
    [dataStore save:existingContact
           response:^(NSDictionary<NSString*, id> *contact) {
               NSLog(@"Contact updated");
           }
           error:^(Fault *fault) {
               NSLog(@"Server reported an error: %@", fault);
           }];
}

 

Deleting Data Objects

The API completely removes an object from the persistent store. If the object is successfully delete, the API returns the timestamp of the exact deletion time in milliseconds.

The Data Service API for deleting an object is available via the Backendless singleton object:

// The singleton reference is defined in Backendless.h:
#define backendless [Backendless sharedInstance]
// Use the following code to obtain the backendless singleton object:
var backendless = Backendless.sharedInstance()

Deleting an object in a data table must be done with a 'data store' object - by using the IDataStore protocol. A reference to a data store can be obtained using the following code:

// obtaining IDataStore for the custom-class approach:
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// obtaining IDataStore for the dictionary-based approach:
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
// obtaining IDataStore for the custom-class approach:
let dataStore = backendless.data of(YOUR-CLASS.ofClass());

// obtaining IDataStore for the map-based approach:
let dataStore = backendless.data ofTable("TABLE-NAME");

where

YOUR-CLASS - identifies a table on the server.
"TABLE-NAME" - the name of the table for which to get the data store. All subsequent data store operations will be performed in the specified table.

All Data Service methods in Backendless must be accessed through IDataStore.

 

A data store object provides the following APIs for deleting data objects in Backendless:

Synchronous Method:

id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// Removes an existing object from Backendless database
NSNumber *num = [dataStore remove:(id)entity];

// Removes an existing object from Backendless database by objectId
NSNumber *num = [dataStore removeById:(NSString *)objectId];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// Removes an existing object from Backendless database
let num = dataStore.remove(entity : Any)

// Removes an object identified by its objectId
let num = dataStore.removeById(objectId: String)

Asynchronous Methods:

id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// Removes an existing object from Backendless database
// Server's response (result or error) is delivered through the block-based callbacks
[dataStore remove:(id)entity 
           response:(void(^)(NSNumber *))responseBlock,
           error:(void(^)(Fault *))errorBlock];

// Removes an object identified by its objectId from the Backendless database
// Server's response (result or error) is delivered through the block-based callbacks
[dataStore removeById:(NSString *)objectId 
           response:(void(^)(NSNumber *))responseBlock,
           error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass()) 

// Removes an object in the Backendless database
// Server's response (result or error) is delivered through the block-based callbacks
dataStore.remove(entity : Any,
                 response:((Any) -> Void)responseBlock,
                 error:((Fault) -> Void)errorBlock) -> Void
 
// Removes an object identified by its objectId
dataStore.removeById(objectId : String,
                     response:((NSNumber) -> Void)responseBlock,
                     error:((Fault) -> Void)errorBlock) -> Void

Synchronous Methods:

id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// Removes an existing object from Backendless database
NSNumber *num = [dataStore remove:(NSDictionary<NSString*, id> *)entity];

// Removes an existing object from Backendless database by objectId
NSNumber *num = [dataStore removeById:(NSString *)objectId];
let dataStore = backendless.data.ofTable("TABLE-NAME")
 
// Removes an object from the Backendless database
let num = dataStore.remove(entity : [String : Any])

// Removes an object identified by its objectId
let num = dataStore.removeById(entity : String)

Asynchronous Methods:

id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// Removes an existing object from Backendless detabse
// Server's response (result or error) is delivered through the block-based callbacks
[dataStore remove:(NSDictionary<NSString*, id> *)entity
           response:(void(^)(NSNumber *))responseBlock,
           error:(void(^)(Fault *))errorBlock];
           
// Removes an object identified by its objectId from the Backendless database
// Server's response (result or error) is delivered through the block-based callbacks
[dataStore removeById:(NSString *)objectId 
           response:(void(^)(NSNumber *))responseBlock,
           error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")

// Removes an object from the Backendless database
// Server's response (result or error) is delivered through the block-based callbacks
dataStore.remove(entity : [String : Any],
                 response:((NSNumber) -> Void)responseBlock,
                 error:((Fault) -> Void)errorBlock)  -> Void

// Removes an object identified by its objectId
dataStore.removeById(objectId: String,
                     response:((NSNumber) -> Void)responseBlock,
                     error:((Fault) -> Void)errorBlock)  -> Void

where:

objectToDelete - Object to delete
objectId - ID of the object to delete
responseBlock - a block to handle successful result of an asynchronous call.
errorBlock - a block to handle fault result of an asynchronous call.

Return Value:

The synchronous method returns the time stamp when the server-side removed the object from the data store. The asynchronous call receives the return value through a callback executed on the either the responder object or through the block-callback.

Example:

Consider the following class which will be used in the example:

@interface Contact : NSObject
@property (nonatomic, strong) NSString *objectId;
@property int age;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *phone;
@end
class Contact : NSObject {
    var objectId : String?
    var age : Int = 0
    var title : String?
    var name : String?
    var phone : String?   
}

The following code deletes an instance of the Contact class from the Backendless object storage:

Remove object from Backendless:
- (void)removeContact:(Contact *)contact {
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore remove:contact
               response:^(NSNumber *num) {
                 NSLog(@"Contact removed");
               }
               error:^(Fault *fault) {
                 NSLog(@"Server reported an error: %@", fault);
               }
     ];
}
Remove object identified by objectId from Backendless:
- (void)removeContactById:(NSString *)contactId {    
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore removeById:contactId
               response:^(NSNumber *num) {
                  NSLog(@"Contact removed");
               }
               error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
               }
     ];
}
Remove object from Backendless:
func removeContact (contact : Contact) {
    let dataStore = self.backendless.data.of(Contact().ofClass())
    dataStore!.remove(contact,
                      response: {
                        (num : NSNumber?) -> () in
                        print("Contact removed")
                      },
                      error: {
                        (fault : Fault?) -> () in
                        print("Server reported an error: \(fault)")
                      })
}
Remove object identified by objectId from Backendless:
func removeContactById (contactId : String) {
    let dataStore = self.backendless.data.of(Contact().ofClass())
    dataStore?.remove(byId: contactId,
                      response: {
                        (num : NSNumber?) -> () in
                        print("Contact removed")
                      },
                      error: {
                        (fault : Fault?) -> () in
                        print("Server reported an error: \(fault)")
                      })
}
With the dictionary/map-driven approach, objects sent to the backend are represented as plain NSDictionary (Obj-C) or untyped object maps (Swift):

- (void)removeMapContact:(NSMutableDictionary<NSString*, id> *)contact {
    id<IDataStore> dataStore = [backendless.data ofTable:@"Contact"];
    [dataStore remove:contact
               response:^(NSNumber *num) {
                 NSLog(@"Contact removed");
               }
               error:^(Fault *fault) {
                 NSLog(@"Server reported an error: %@", fault);
               }
     ];
}

Retrieving Schema Definition

Backendless provides API for data table schema introspection. The API returns information about table's columns and their data types, whether a value for a column is required or if there is a default value.

Synchronous Method Signatures:

// Retrieve schema for a table identified by your class
// Class name must match the table name
NSArray<ObjectProperty *> *schemaProps = [backendless.data describe:@"TABLE-NAME"];
// Retrieve schema for a table identified by your class
// Class name must match the table name
let schemaProps = backendless.data.describe(entityName: String)

Asynchronous Method Signatures:

// Retrieve schema for a table identified by the class name
// If server returns an error, return it via the block-based callbacks
[backendless.data describe:@"TABLE-NAME"]
                  response:(void(^)(NSArray<ObjectProperty *> *schemaProps)) responseBlock,
                  error:^(void(^)(Fault *))errorBlock];
// Retrieve schema for a table identified by the class name
// If server returns an error, return it via the block-based callbacks
backendless.data.describe(entityName: String,
                          response:(([ObjectProperty]) -> Void)responseBlock,
                          error:((Fault) -> Void)errorBlock

where:

YOUR-CLASS - class identifying the table on the server side. Class name must match the table name.
tableName - name of a data table to get the schema for
fault - an instance of Fault describing an error reported by the server

 

Return value:

The method returns an array of the ObjectProperty objects. The ObjectProperty protocol has the following properties:

autoLoad - applies only to relations. If true, the property is set to auto-load related data for the data retrieval queries.
customRegex - a regular expression assigned to the column as a validator. The validator applies when a new object is saved in the table or an existing one is updated.
defaultValue - a default value assigned to any object saved/updated in the table where the column does not have a value.
identity - true if the column is or is a part of a primary key.
name - contains the name of a property.
relatedTable - contains the name of the related table(s).
required - A boolean value defining whether a property is optional or required for the requests which save the initial object or update an existing one.
type:ObjectDataType - defines the column type.

Example:

Suppose the Person table has the following schema - there are two developer-defined columns, age and name
person-schema.zoom70
The following code retrieves the schema definition for the Person table:

[backendless.data describe:@"Person"
   response:^(NSArray<ObjectProperty *> *schemaProps) {
     for (ObjectProperty *property in schemaProps) {
       NSLog(@"property name - %@", property.name);               
       NSLog(@"  is property required - %s", property.isRequired ? "true" : "false");
       NSLog(@"  property data type - %@", property.objectDataType);
       NSLog(@"  default value - %@", property.defaultValue);
       NSLog(@"  is property primary key - %s", property.isPrimaryKey ? "true" : "false");
     }
   }
   error:^(Fault *fault) {
       NSLog(@"Server reported an error: %@", fault);
   }];
backendless.data.describe("Person",
    response: {
       (schemaProps) -> () in
         for property : ObjectProperty in schemaProps! {  
           print("property name - \(property.name)")
           print("  is property required - \(property.isRequired())")
           print("  property data type - \(property.objectDataType())")
           print("  default value - \(property.defaultValue)")
           print("  default value - \(property.defaultValue)")
           print("  is property primary key - \(property.isPrimaryKey())")   
         }
    },
    error: {
       (fault : Fault?) -> () in
       print("Server reported an error: \(fault)")
    })

The code produces the following output:

property name - objectId
  is property required - false
  property data type - STRING_ID
  default value - null
  is property primary key - true
property name - age
  is property required - false
  property data type - INT
  default value - null
  is property primary key - false
property name - updated
  is property required - false
  property data type - DATETIME
  default value - null
  is property primary key - false
property name - name
  is property required - false
  property data type - STRING
  default value - null
  is property primary key - false
property name - created
  is property required - false
  property data type - DATETIME
  default value - null
  is property primary key - false
property name - ownerId
  is property required - false
  property data type - STRING
  default value - null
  is property primary key - false

 

Get Object Count

The Object Count API provides a way to obtain the following values from the server:

Number of objects in a table
Number of objects matching query
Number of related objects

 

Backendless singleton object is the entry point for all Backendless APIs:

// The singleton reference is defined in Backendless.h:
#define backendless [Backendless sharedInstance]
// Use the following code to obtain the backendless singleton object:
var backendless = Backendless.sharedInstance()

Retrieving object count for a table/query can be done with a 'data store' object - by using the IDataStore protocol. A reference to a data store can be obtained using the following code:

// obtaining IDataStore for the custom-class approach:
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// obtaining IDataStore for the dictionary-based approach:
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
// obtaining IDataStore for the custom-class approach:
let dataStore = backendless.data of(YOUR-CLASS.ofClass());

// obtaining IDataStore for the map-based approach:
let dataStore = backendless.data ofTable("TABLE-NAME");

where

YOUR-CLASS - identifies a table on the server.
"TABLE-NAME" - the name of the table for which to get the data store. All subsequent data store operations will be performed in the specified table.

All Data Service methods in Backendless must be accessed through IDataStore.

 

A data store object provides the following APIs for retrieving object counts in Backendless:

Synchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
 
// Get total number of objects in a table identified by the class
NSNumber *objectCount = [dataStore getObjectCount];

// Get number of objects for a query
NSNumber *objectCountForQuery = [dataStore getObjectCount:(DataQueryBuilder *)queryBuilder];
// Get the data store for a table
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// Get total number of objects in a table identified by the class/dataStore
let objectCount = dataStore.getObjectCount()

// Get number of objects for a query
let objectCountForQuery = dataStore.getObjectCount(queryBuilder : DataQueryBuilder)

Asynchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
 
// get total number of objects in a table identified by the dataStore object
[dataStore getObjectCount:^(NSNumber *)responseBlock
                    error:^(Fault *)errorBlock];
                    
// get number of objects for a query in the table identified by the dataStore object
[dataStore getObjectCount:(DataQueryBuilder *)queryBuilder
                   response:^(NSNumber *)responseBlock
                   error:^(Fault *)errorBlock];
// Get the data store for a table identified by class
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
 
// get total number of objects in the table identified by dataStore
dataStore.getObjectCount(((NSNumber) -> Void) responseBlock,
                          error:((Fault) -> Void)errorBlock) -> Void
                          
// get number of objects for a query
dataStore.getObjectCount(queryBuilder: DataQueryBuilder,
                         response:((NSNumber) -> Void) responseBlock,
                         error:((Fault) -> Void)errorBlock) -> Void

Synchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// Get total number of objects in a table
NSNumber *objectCount = [dataStore getObjectCount];

// Get number of objects for a query 
NSNumber *objectCountForQuery = [dataStore getObjectCount:(DataQueryBuilder *)queryBuilder];
// Get the data store for a table
let dataStore = backendless.data.ofTable("TABLE-NAME")

// Get total number of objects in the table identified by dataStore
let objectCount = dataStore.getObjectCount()

// Get total number of objects for a query
let objectCountForQuery = dataStore.getObjectCount(queryBuilder : DataQueryBuilder)

Asynchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// get total number of objects in the table identified by the dataStore object
[dataStore getObjectCount:^(NSNumber *)responseBlock
                    error:^(Fault *)errorBlock];
                    
// get number of objects for a query 
[dataStore getObjectCount:(DataQueryBuilder *)queryBuilder
                    response:^(NSNumber *)responseBlock
                    error:^(Fault *)errorBlock];
// Get the data store for a table
let dataStore = backendless.data.ofTable("TABLE-NAME")

// get total number of objects in the table identified by dataStore
dataStore.getObjectCount(((NSNumber) -> Void) responseBlock,
                          error:((Fault) -> Void)errorBlock) -> Void
                          
// get number of objects for a query 
dataStore.getObjectCount(queryBuilder: DataQueryBuilder,
                         response:((NSNumber) -> Void) responseBlock,
                         error:((Fault) -> Void)errorBlock) -> Void

where:

responseBlock - a block to handle successful result of an asynchronous call.
errorBlock - a block to handle fault result of an asynchronous call.

Return Value:

The blocking/synchronous methods returns an integer value which is the object count. A non-blocking/asynchronous call receives a callback with the object count value.

Examples:

Total object count for a table:
The following sample request retrieves total number of objects in table Order:
- (void)getObjectCount {
    id<IDataStore> dataStore = [backendless.data of:[Order class]];
    [dataStore getObjectCount:^(NSNumber *objectCount) {
        NSLog(@"Total objects in the order table = %@", objectCount);
    }
                        error:^(Fault *fault) {
                            NSLog(@"Server reported an error: %@", fault);
                        }
     ];
}
Object count for a query:
The following sample request retrieves total number of objects in table Order which satisfy the condition of orderAmount > 100:
- (void)getObjectCountQuery {
    id<IDataStore> dataStore = [backendless.data of:[Order class]];
    DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
    [queryBuilder setWhereClause:@"orderAmount > 100"];
    [dataStore getObjectCount:queryBuilder
                     response:^(NSNumber *objectCount) {
                         NSLog(@"Found objects: %@", objectCount);
                     }
                        error:^(Fault *fault) {
print("Server reported an error: \(fault)")
                        }
     ];
}
Related object count:
The following sample request retrieves total number of related "child" objects for a parent object. The parent table is Person. It contains a relation column called address pointing to the Address table. The query below retrieves a count of related child objects for a parent object with objectID of XXXX-XXXX-XXXX-XXXX. The whereClause query syntax for this scenario is:
Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'
- (void)getObjectCountRelated {
    id<IDataStore> dataStore = [backendless.data of:[Address class]];
    DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
    [queryBuilder setWhereClause:@"Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'"];
    [dataStore getObjectCount:queryBuilder
                     response:^(NSNumber *objectCount) {
                         NSLog(@"Found objects: %@", objectCount);
                     }
                        error:^(Fault *fault) {
                        }
     ];
}
Total object count for a table:
The following sample request retrieves total number of objects in table Order:
func getObjectCount () {
    let dataStore = self.backendless.persistenceService.of(Order().ofClass())
    dataStore?.getObjectCount({
        (objectCount : NSNumber?) -> () in
        print("Total objects in the order table = \(objectCount)")
    },
                              error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
    })
}
Object count for a query:
The following sample request retrieves total number of objects in table Order which satisfy the condition of orderAmount > 100:
func getObjectCountQuery () {
    let dataStore = self.backendless.persistenceService.of(Order().ofClass())
    let queryBuilder = DataQueryBuilder()
    dataStore?.getObjectCount(queryBuilder?.setWhereClause("orderAmount > 100"),
                              response: {
                                (objectCount : NSNumber?) -> () in
                                print("Found objects: \(objectCount)")
    },
                              error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
    })
}
Related object count:
The following sample request retrieves total number of related "child" objects for a parent object. The parent table is Person. It contains a relation column called address pointing to the Address table. The query below retrieves a count of related child objects for a parent object with objectID of XXXX-XXXX-XXXX-XXXX. The whereClause query syntax for this scenario is:
Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'
func getObjectCountRelated () {        
  let dataStore = self.backendless.persistenceService.of(Address().ofClass())
  let queryBuilder = DataQueryBuilder()
  queryBuilder = queryBuilder?.setWhereClause("Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'")
  dataStore?.getObjectCount( queryBuilder,
                                  response: {
                                    (objectCount : NSNumber?) -> () in
                                    print("Found objects: \(objectCount)")
        },
                                  error: {
                                    (fault : Fault?) -> () in
                                    print("Server reported an error: \(fault)")
        })
    }  
Total object count for a table:
The following sample request retrieves total number of objects in table Order:
- (void)getMapObjectCount {
    id<IDataStore> dataStore = [backendless.data ofTable:@"Order"];
    [dataStore getObjectCount:^(NSNumber *objectCount) {
        NSLog(@"Total objects in the order table = %@", objectCount);
    }
                        error:^(Fault *fault) {
                            NSLog(@"Server reported an error: %@", fault);
                        }
     ];
}
Object count for a query:
The following sample request retrieves total number of objects in table Order which satisfy the condition of orderAmount > 100:
- (void)getMapObjectCountQuery {
    id<IDataStore> dataStore = [backendless.data ofTable:@"Order"];    
    DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
    [queryBuilder setWhereClause:@"orderAmount > 100"];
    [dataStore getObjectCount:queryBuilder
                     response:^(NSNumber *objectCount) {
                         NSLog(@"Found objects: %@", objectCount);
                     }
                        error:^(Fault *fault) {
NSLog(@"Server reported an error: %@", fault);
                        }
     ];
}
Related object count:
The following sample request retrieves total number of related "child" objects for a parent object. The parent table is Person. It contains a relation column called address pointing to the Address table. The query below retrieves a count of related child objects for a parent object with objectID of XXXX-XXXX-XXXX-XXXX. The whereClause query syntax for this scenario is:
Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'
- (void)getMapObjectCountRelated {
    id<IDataStore> dataStore = [backendless.data ofTable:@"Address"];
    DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
    [queryBuilder setWhereClause:@"Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'"];
    [dataStore getObjectCount:queryBuilder
                     response:^(NSNumber *objectCount) {
                         NSLog(@"Found objects: %@", objectCount);
                     }
                        error:^(Fault *fault) {
                        }
     ];
}
Total object count for a table:
The following sample request retrieves total number of objects in table Order:
func getMapObjectCount () {    
    let dataStore = self.backendless.persistenceService.ofTable("Order")
    dataStore?.getObjectCount({
        (objectCount : NSNumber?) -> () in
        print("Total objects in the order table = \(objectCount)")
    },
                              error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
    })    
}
Object count for a query:
The following sample request retrieves total number of objects in table Order which satisfy the condition of orderAmount > 100:
func getMapObjectCountQuery () {
    let dataStore = self.backendless.persistenceService.ofTable("Order")
    let queryBuilder = DataQueryBuilder()
    dataStore?.getObjectCount(queryBuilder?.setWhereClause("orderAmount > 100"),
                              response: {
                                (objectCount : NSNumber?) -> () in
                                print("Found objects: \(objectCount)")
    },
                              error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
    })
}
Related object count:
The following sample request retrieves total number of related "child" objects for a parent object. The parent table is Person. It contains a relation column called address pointing to the Address table. The query below retrieves a count of related child objects for a parent object with objectID of XXXX-XXXX-XXXX-XXXX. The whereClause query syntax for this scenario is:
Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'
func getMapObjectCountRelated () {
  let dataStore = self.backendless.persistenceService.ofTable("Address")
  let queryBuilder = DataQueryBuilder()
  queryBuilder = queryBuilder?.setWhereClause("Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'")
  dataStore?.getObjectCount(queryBuilder,
                            response: {
                              (objectCount : NSNumber?) -> () in
                              print("Found objects: \(objectCount)")
        },
                            error: {
                              (fault : Fault?) -> () in
                              print("Server reported an error: \(fault)")
        })
    }

 

Basic Object Retrieval

Backendless supports multiple basic search operations. These include finding an object by ID, finding first or last object in the collection or retrieving the entire persisted collection. Each method is available in both synchronous and asynchronous versions:

Retrieving Data Objects

Backendless singleton object is the entry point for all Backendless APIs:

// The singleton reference is defined in Backendless.h:
#define backendless [Backendless sharedInstance]
// Use the following code to obtain the backendless singleton object:
var backendless = Backendless.sharedInstance()

Retrieving object count for a table/query can be done with a 'data store' object - by using the IDataStore protocol. A reference to a data store can be obtained using the following code:

// obtaining IDataStore for the custom-class approach:
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// obtaining IDataStore for the dictionary-based approach:
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];
// obtaining IDataStore for the custom-class approach:
let dataStore = backendless.data of(YOUR-CLASS.ofClass());

// obtaining IDataStore for the map-based approach:
let dataStore = backendless.data ofTable("TABLE-NAME");

where

YOUR-CLASS - identifies a table on the server.
"TABLE-NAME" - the name of the table for which to get the data store. All subsequent data store operations will be performed in the specified table.

All Data Service methods in Backendless must be accessed through IDataStore.

 

A data store object provides the following APIs for basic data retrieval from Backendless:

Basic retrieval of data objects

Paging is built-in and the server returns up to 100 objects at a time.

id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// SYNC METHOD
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is thrown as an exception
NSArray *objectArray = [dataStore find];

// ASYNC METHOD
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore find:(void(^)(NSArray<YOUR-CLASS>))responseBlock
                 error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// SYNC METHOD
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is thrown as an exception
[YOUR-CLASS] = dataStore.find();

// ASYNC METHOD
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.find((([YOUR-CLASS) -> Void) responseBlock,
          error:((Fault) -> Void)errorBlock) -> Void

Find first data object

The first data object is the first one saved in the data store:

id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// SYNC METHOD
// ===========================================================
// retrieve the first object from the data store. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS firstObj = [dataStore findFirst];

// ASYNC METHOD
// ===========================================================
// retrieve the first object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore findFirst:(void(^)(YOUR-CLASS))responseBlock
           error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// SYNC METHOD
// ===========================================
// retrieve the first object from the data store. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS firstObj = dataStore.findFirst();

// ASYNC METHODS
// ===========================================
// retrieve the first object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.findFirst(((YOUR-CLASS) -> Void) responseBlock,
                     error:((Fault) -> Void)errorBlock) -> Void

Find last data object

The last data object is the last one saved in the data store:

id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// SYNC METHOD
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS lastObj = [dataStore findLast];

// ASYNC METHOD
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore findLast:(void(^)(YOUR-CLASS))responseBlock
                     error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// SYNC METHOD
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS lastObj = dataStore.findLast();

// ASYNC METHOD
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.findLast((YOUR-CLASS) -> Void) responseBlock,
                    error:((Fault) -> Void)errorBlock) -> Void

Find a data object by object  ID

id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];

// SYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS dataObject = [dataStore findById:(id)objectId];

// ASYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore findById:(id)objectId response:(void(^)(YOUR-CLASS))responseBlock
                    error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// SYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS dataObject = dataStore.findById(_ objectId: String)

// ASYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.findById(objectId: String,
                   response:((YOUR-CLASS) -> Void)responseBlock,
                   error:((Fault!) -> Void)!)errorBlock) -> Void

Basic retrieval of data objects

Paging is built-in and the server returns up to 100 objects at a time.

id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// SYNC METHODS
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is thrown as an exception
NSArray<NSDictionary<NSString *, id>> arrayOfObjects [dataStore find];

// ASYNC METHODS
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore find:(void(^)(NSArray<NSDictionary<NSString *, id> *>))responseBlock
           error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")

// SYNC METHODS
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is thrown as an exception
[YOUR-CLASS] arrayOfObjects = dataStore.find();

// ASYNC METHODS
// ===========================================================
// retrieve a "page" of objects. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.find((([YOUR-CLASS]) -> Void) responseBlock,
          error:((Fault) -> Void)errorBlock) -> Void)

Find first data object

The first data object is the first one saved in the data store:

id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// SYNC METHOD
// ===========================================================
// retrieve the first object from the data store. If server returns an error, 
// it is thrown as an exception
NSDictionary<NSString *, id> firstObject = [dataStore findFirst];

// ASYNC METHOD
// ===========================================================
// retrieve the first object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore findFirst:(void(^)(NSDictionary<NSString *, id>))responseBlock
           error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")

// SYNC METHOD
// ===========================================================
// retrieve the first object from the data store. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS firstObject = findFirst();

// ASYNC METHOD
// ===========================================================
// retrieve the first object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.findFirst(((YOUR-CLASS) -> Void) responseBlock,
          error:((Fault) -> Void)errorBlock) -> Void)

Find last data object

The last data object is the last one saved in the data store:

id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// SYNC METHOD
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS lastObject = [dataStore findLast];

// ASYNC METHOD
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore findLast:(void(^)(YOUR-CLASS))responseBlock
           error:(void(^)(Fault *))errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")

// SYNC METHODS
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS lastObject = dataStore.findLast();

// ASYNC METHOD
// ===========================================================
// retrieve the last object from the data store. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.findLast((YOUR-CLASS) -> Void) responseBlock,
           error:((Fault) -> Void)errorBlock) -> Void

Find a data object by object  ID

id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// SYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is thrown as an exception
NSDictionary<NSString *, id> dataObject = [dataStore findById:(id)objectId];

// ASYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is delivered through the block-based callbacks
[dataStore findById:(id)objectId response:(void(^)(NSDictionary<NSString *, id>))responseBlock
           error:(void(^)(Fault *))errorBlock];

let dataStore = backendless.data.ofTable("TABLE-NAME")

// SYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is thrown as an exception
YOUR-CLASS dataObject = dataStore.findById(objectId: String);

// ASYNC METHOD
// ===========================================================
// retrieve object by its object ID. If server returns an error, 
// it is delivered through the block-based callbacks
dataStore.findById(objectId: String,
                   response:((YOUR-CLASS) -> Void)responseBlock,
                   error:((Fault!) -> Void)!)errorBlock) -> Void;

where:

objectId - object ID of the object to find. For the details about objectId, see the Data Object section of the documentation.
responseBlock - a block to handle successful result of an asynchronous call.
errorBlock - a block to handle fault result of an asynchronous call.

Return Value:

Most methods return a single object (findFirst, findList, findById), the find method returns an array of objects. The array contains objects of the specified class with the "Custom class approach" or dictionaries/maps with the "Dictionary approach". The collection is an instance of the BackendlessCollection class. For details on working with collections, see the Collections section of the guide.

Example:

Consider the following class:

@interface Contact: NSObject
@property (nonatomic, strong) NSString *objectId;
@property (nonatomic, strong) NSDate *created;
@property (nonatomic, strong) NSDate *updated;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *phone;
@property (nonatomic) NSUInteger age;
@end

@implementation Contact
@end

The following code demonstrates various search queries:

Example: Load objects from the "Contact" table:

- (void)find {
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore find:^(NSArray<Contact *> *resultsArray) {
        NSLog(@"Result: %@", resultsArray);
    }
              error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
              }
     ];
}

Example: Find the first object in the "Contact" table:

- (void)findFirst {
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore findFirst:^(Contact *contact) {
        NSLog(@"First contact: %@", contact);
    }
                   error:^(Fault *fault) {
                       NSLog(@"Server reported an error: %@", fault);
                   }
     ];
}

Example: Find the last object in the "Contact" table:

- (void)findLast {
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore findLast:^(Contact *contact) {
        NSLog(@"Last contact: %@", contact);
      }
      error:^(Fault *fault) {
        NSLog(@"Server reported an error: %@", fault);
      }];
}

Example: Find object by object ID:

- (void)findById {
    id<IDataStore> dataStore = [backendless.data of:[Contact class]];
    [dataStore findById:@"06D11834-A8FA-BF43-FF62-7D372DB55300"
               response:^(Contact *contact) {
                   NSLog(@"Result: %@", contact);
               }
               error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault);
               }
     ];
}

Consider the following class:

class Contact: NSObject {
    var objectId : String?
    var age : Int = 0
    var title : String?
    var name : String?
    var phone : String?
}

The following code demonstrates various search queries:

Example: Load objects from the "Contact" table:

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.find({
      (array) -> () in 
      let contactsArray = array as! [Contact]
      print("Result: \(contactsArray)") 
   },
   error: {
      (fault : Fault?) -> () in
      print("Server reported an error: \(fault)")
   })

Example: Find the first object in the "Contact" table:

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.findFirst({
   (contact) -> () in
      let firstContact = contact as! Contact
      print("Result: \(firstContact)")
   },
   error: {
      (fault : Fault?) -> () in
      print("Server reported an error: \(fault)")
   })

Example: Find the last object in the "Contact" table:

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.findLast({
   (contact) -> () in
      let lastContact = contact as! Contact
      print("Result: \(lastContact)")
   },
   error: {
      (fault : Fault?) -> () in
      print("Server reported an error: \(fault)")
   })

Example: Find object by object ID:

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.findById( "06D11834-A8FA-BF43-FF62-7D372DB55300",
   response: {
      (contact) -> () in
      let contactObject = contact as! Contact
      print("Result: \(contactObject)")
   },
   error: {
      (fault : Fault?) -> () in
      print("Server reported an error: \(fault)")
   })

Dictionary-driven approach does not require a class definition for objects stored in Backendless. The following code demonstrates various search queries:

Example: Load objects from the "Contact" table:

id<IDataStore> dataStore = [backendless.data ofTable:@"Contact"];
    [dataStore find:^(NSArray<NSDictionary<NSString *, id> *> *resultsArray) {
        NSLog(@"Result: %@", resultsArray);
    }
    error:^(Fault *fault) {
        NSLog(@"Server reported an error: %@", fault);
    }];

Example: Find the first object in the "Contact" table:

id<IDataStore> dataStore = [backendless.data ofTable:@"Contact"];
[dataStore findFirst:^(NSDictionary<NSString *, id> *contact) {
    NSLog(@"First contact: %@", contact);
  }
  error:^(Fault *fault) {
    NSLog(@"Server reported an error: %@", fault);
  }];

Example: Find the last object in the "Contact" table:

id<IDataStore> dataStore = [backendless.data ofTable:@"Contact1"];
[dataStore findLast:^(NSDictionary<NSString *, id> *contact) {
    NSLog(@"Last contact: %@", contact);
  }
  error:^(Fault *fault) {
    NSLog(@"Server reported an error: %@", fault);
  }];

Example: Find object by object ID:

id<IDataStore> dataStore = [backendless.data ofTable:@"Contact"];
[dataStore findById:@"06D11834-A8FA-BF43-FF62-7D372DB55300"
  response:^(NSDictionary<NSString *, id> *contact) {
    NSLog(@"Result: %@", contact);
  }
  error:^(Fault *fault) {
    NSLog(@"Server reported an error: %@", fault);
  }];

Dictionary-driven approach does not require a class definition for objects stored in Backendless. The following code demonstrates various search queries:

Example: Load objects from the "Contact" table:

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.find({
     (array) -> () in
      let contactArray = array as! [[String : Any]]
      print("Result: \(contactArray)")
  },
  error: {
     (fault : Fault?) -> () in
     print("Server reported an error: \(fault)")
  })

Example: Find the first object in the "Contact" table:

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.findFirst({
   (dictionary) -> () in
   let contactAsDictionary =  dictionary as! [String : Any]
   print("Result: \(contactAsDictionary)")
 },
 error: {
   (fault : Fault?) -> () in
   print("Server reported an error: \(fault)")
})

Example: Find the last object in the "Contact" table:

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.findLast({
    (dictionary) -> () in
    let contactAsDictionary =  dictionary as! [String : Any]
    print("Result: \(contactAsDictionary)")
  },
  error: {
    (fault : Fault?) -> () in
    print("Server reported an error: \(fault)")
  })

Example: Find object by object ID:

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.findById( "A6D9727A-8E59-5D89-FFCF-14EF8693BC00",
    response: {
       (dictionary) -> () in
       let contactAsDictionary =  dictionary as! [String : Any]
       print("Result: \(contactAsDictionary)")
    },
    error: {
       (fault : Fault?) -> () in
       print("Server reported an error: \(fault)")
    })

 

Advanced Object Retrieval

Advanced search use-cases supported by Backendless include:

Search with a query (a "where clause" search) -retrieves data objects which satisfy a condition.
Paged data retrieval - retrieves a "page" of data of the given size from the specified offset.
Sorted data object retrieval - retrieves a collection of data objects sorted by specified properties.
Retrieval of related objects - fetching data objects through special "relation" properties. See retrieval of related data objects.

 

Backendless supports data retrieval options listed above with a special class - DataQueryBuilder. An instance of DataQueryBuilder can be passed into the find operation to retrieve data from the server according to the options set in the query builder object. The class provides the following methods:

build - creates a new instance of DataQueryBuilder.
setWhereClause - sets a search query. A query must be in the SQL-92 syntax (the "where" clause part).
setSortBy - sets an array of column names to sort the data objects in the response by.
setRelated - sets an array of related columns names. Objects from the related columns are included into the response.
setRelationsDepth - sets the number of "levels" in the hierarchy of related objects to include into the response.
setPageSize - sets the page size - which is the number of objects to return in the response. Maximum value is 100.
setOffset - sets the offset - an index in the server-side storage from where the data objects should be retrieved.

 

To run a query-based search, use the following API methods:

Synchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
 
// Retrieve objects for the parameters set in query builder
NSArray *dataObjects = [dataStore find:(DataQueryBuilder *)queryBuilder];
// Get the data store for a table
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())

// Retrieve objects for the parameters set in query builder
[YOUR-CLASS] dataObjects = dataStore.find(queryBuilder : DataQueryBuilder);

Asynchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data of:[YOUR-CLASS class]];
 
// Get data objects for the parameters in query builder
[dataStore find:(DataQueryBuilder *)queryBuilder 
                        response:(void(^)(NSArray *))responseBlock 
                        error:(void(^)(Fault *))errorBlock];
// Get the data store for a table identified by class
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
 
// Get data objects for the parameters in query builder
dataStore.find( queryBuilder:DataQueryBuilder,
                response:(([YOUR-CLASS) -> Void) responseBlock,
                error:((Fault) -> Void)errorBlock) -> Void)

Synchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// Get data objects for the parameters in query builder
NSArray<NSDictionary<NSString *, id>> *dataObjects;
dataObjects = [dataStore find:(DataQueryBuilder *)queryBuilder];
// Get the data store for a table
let dataStore = backendless.data.ofTable("TABLE-NAME")

// Get data objects for the parameters in query builder
[String : Any] dataObjects = dataStore.find(queryBuilder : DataQueryBuilder)

Asynchronous Method:

// Get the data store for the table
id<IDataStore> dataStore = [backendless.data ofTable:@"TABLE-NAME"];

// Get data objects for the parameters in query builder
[dataStore find:(DataQueryBuilder *)queryBuilder
                 response:void(^)(NSArray<NSDictionary<NSString *, id> *>))responseBlock
                 error:(void(^)(Fault *))errorBlock;
// Get the data store for a table
let dataStore = backendless.data.ofTable("TABLE-NAME")

// Get data objects for the parameters in query builder
dataStore.find(queryBuilder: DataQueryBuilder,
               response:(([[String : Any]]) -> Void) responseBlock,
               error:((Fault) -> Void)errorBlock) -> Void

where:

queryBuilder - an instance of DataQueryBuilder - contains the search query and other search options.
responder - a responder object which will receive a callback when the search operation returns the result. Applies to the asynchronous method only.
responseBlock - a block to handle successful result of an asynchronous call.
errorBlock - a block to handle fault result of an asynchronous call.

Return Value:

Returns a collection of strongly typed data objects retrieved as a result of the query execution.

Examples

For examples on using the advanced object retrieval API, see the following sub-sections:

Search with the Where clause

A where clause is a condition which references columns of a data table where the query is sent. For any given query Backendless returns the data records which match the condition in the where clause. A where clause is a query string which must conform to a subset of the SQL-92 standard. The subset of the syntax supported by Backendless is the part that goes into the WHERE clause of an SQL query. This is the reason why we refer to it as the where clause. For example, consider the following data objects, specifically notice the columns name and age:

sample-table.zoom70

 

Suppose you need to retrieve an object where the value of the name property is "Joe". This can be accomplished with the following where clause:

name = 'Joe'

 

Or if you need all objects where the age column contains a value less than 30:

age < 30

 

Just like in SQL, columns can be combined in a single where clause. The following condition would retrieve all objects containing letter J in the name and the age column's value is greater than 20:

name LIKE 'J%' and age > 20

 

Condition

Sample where clause

Column type

Value in a column equals a string literal.

name = 'foo'

STRING, TEXT or EXTENDED STRING

Value in a column does not equal a string literal.

name != 'foo'

STRING, TEXT or EXTENDED STRING

Value in a column is not assigned

name is null

STRING, TEXT or EXTENDED STRING

Value in a column is assigned

name is not null

STRING, TEXT or EXTENDED STRING

Value in a column contains a substring literal

name LIKE '%foo%'

STRING, TEXT or EXTENDED STRING

Value in a column ends with a substring literal

name LIKE '%foo'

STRING, TEXT or EXTENDED STRING

Value in a column starts with a substring literal

name LIKE 'foo%'

STRING, TEXT or EXTENDED STRING

Value in a column is one of enumerated string literals.

name IN ('value1', 'value2', 'value3)
 
or
 
name = 'value1' OR name = 'value2' or name = 'value3'

STRING, TEXT or EXTENDED STRING

Value in a column equals a numeric literal

age = 21

INT or DOUBLE

Value in a column is greater than a numeric literal

age > 21

INT or DOUBLE

Value in a column is greater than or equal a numeric literal

age >= 21

INT or DOUBLE

Value in a column is less than a numeric literal

age < 21

INT or DOUBLE

Value in a column is less than or equal a numeric literal

age <= 21

INT or DOUBLE

 

Find all contacts where the value of the "age" property equals 47:
NSString *whereClause = @"age = 47";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data of[Contact class]];
[dataStore find:queryBuilder
       response:^(NSArray<Contact *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
       }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];
Find all contacts where the value of the "age" property is greater than 21:
NSString *whereClause = @"age > 21";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data of[Contact class]];
[dataStore find:queryBuilder
       response:^(NSArray<Contact *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
       }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];
Find all contacts by name:
NSString *whereClause = @"name = 'Jack Daniels'";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data of[Contact class]];
[dataStore find:queryBuilder
       response:^(NSArray<Contact *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
       }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];

Find all contacts by partial name match:
NSString *whereClause = @"name LIKE 'Jack%'";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data of[Contact class]];
[dataStore find:queryBuilder
       response:^(NSArray<Contact *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
       }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];
Find all contacts where the value of the "age" property equals 47:
let whereClause = "age = 47"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [Contact]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})
Find all contacts where the value of the "age" property is greater than 21:
let whereClause = "age > 21"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [Contact]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})
Find all contacts by name:
let whereClause = "name = 'Jack Daniels'"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [Contact]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})
Find all contacts by partial name match:
let whereClause = "name LIKE 'Jack%'"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [Contact]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})

Find all contacts where the value of the "age" property equals 47:
NSString *whereClause = @"age = 47";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data ofTable:@"Contact"];
[dataStore find:queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
        }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];
Find all contacts where the value of the "age" property is greater than 21:
NSString *whereClause = @"age > 21";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data ofTable:@"Contact"];
[dataStore find:queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
        }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];
Find all contacts by name:
NSString *whereClause = @"name = 'Jack Daniels'";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data ofTable:@"Contact"];
[dataStore find:queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
        }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];
Find all contacts by partial name match:
NSString *whereClause = @"name LIKE 'Jack%'";
DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
[queryBuilder setWhereClause:whereClause];
    
id<IDataStore>dataStore = [backendless.data ofTable:@"Contact"];
[dataStore find:queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *foundContacts) {
           NSLog(@"Result: %@", foundContacts);
        }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
];
Find all contacts where the value of the "age" property equals 47:
let whereClause = "age = 47"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [[String : Any]]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})
Find all contacts where the value of the "age" property is greater than 21:
let whereClause = "age > 21"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [[String : Any]]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})
Find all contacts by name:
let whereClause = "name = 'Jack Daniels'"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [[String : Any]]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})
Find all contacts by partial name match::
let whereClause = "name LIKE 'Jack%'"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)

let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.find(queryBuilder,
                response: {
                    (foundContacts) -> () in
                    let foundConts = foundContacts as! [[String : Any]]
                    print("Result: \(foundConts)")
},
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
})

 

Using Dates in Search

There is a special consideration for the whereClause-based queries which reference a column of the DATETIME data type. Typically a DATETIME column is referenced in a comparison against a scalar value describing a specific date or a timestamp. The scalar value can be a number of milliseconds since the epoch (UNIX timestamp as milliseconds) or a string. Backendless supports a variety of date formats for the latter. For example, the queries below will find all the objects which were updated after March 23rd, 2015:

updated > '23-Mar-2015'

updated > '03/23/2015'

updated > 1427068800000

Comparison Operators

Backendless supports the following date comparison operators:

Column's value is after the specified date/time: use either > or the after keyword:

birthDate > '22-Dec-1980'

birthDate after 1427068800000

Column's value is before the specified date/time: use either < or the before keyword:

birthDate < '22-Dec-1980'

birthDate before 1427068800000

Column's value is either at or after the specified date/time: use either => or the at or after keyword:

birthDate >= '28-10-1988'

birthDate at or after '10/28/1988 00:00:00 GMT-0200'

Column's value is either at or before the specified date/time: use either <= or the at or before keyword:

birthDate >= '28-10-1988'

birthDate at or after '10/28/1988 00:00:00 GMT-0200'

Note: the whereClause-based queries can be tested in the Backendless Console with the SQL Search turned on.

Supported Date Formats

Date/time string values may be in any of the following formats. The pattern letters have the same definition as in Java's SimpleDateFormat:

EEE MMM dd HH:mm:ss zzz yyyy
yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
MM/dd/yyyy HH:mm:ss 'GMT'z
MM.dd.yyyy HH:mm:ss 'GMT'z
MM-dd-yyyy HH:mm:ss 'GMT'z
MM/dd/yyyy HH:mm:ss z
MM.dd.yyyy HH:mm:ss z
MM.dd.yyyy HH:mm:ss
MM-dd-yyyy HH:mm:ss
MM/dd/yyyy HH:mm:ss
MM.dd.yyyy
MM-dd-yyyy
MM/dd/yyyy HH:mm:ss 'GMT'Z
MM/dd/yyyy HH:mm
MM/dd/yyyy
dd/MMM/yyyy
dd-MMM-yyyy
EEEEE, d MMMMM yyyy
yyyy/MM/d/HH:mm:ss
yyyy-MM-dd'T'HH:mm:ss
EEEEE, MMMMM d, yyyy
MMMMM d, yyyy
yyyy M d
yyyyMMMd
yyyy-MMM-d
yyyy-M-d, E
'Date' yyyy-MM-dd
yyyy-MM-dd'T'HH:mm:ssZ
yyyy-MM-dd'T'HH:mmZ
yyyy-MM-dd
yyyy-'W'w
yyyy-DDD
d MMMMM yyyy, HH'h' mm'm' ss's'

Example

DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
NSDateFormatter *dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = @"yyyyMMdd";
NSString *dateString = [dateFormatter stringFromDate:[[NSDate date] dateByAddingTimeInterval:-30*24*60*60]];
[queryBuilder setWhereClause:[NSString stringWithFormat:@"created > '%@'", dateString]];
id<IDataStore>dataStore = [backendless.data of:[Contact class]];
[dataStore find:queryBuilder
    response:^(NSArray<Contact *> *dataObjects) {
       NSLog(@"Result: %@", dataObjects);
    }
    error:^(Fault *fault) {
       NSLog(@"Server reported an error: %@", fault);
    }
];
let queryBuilder = DataQueryBuilder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
let dateString = dateFormatter.string(from: (NSDate() as Date).addingTimeInterval(-30*24*60*60))
queryBuilder!.setWhereClause(String(format: "created > '%@'", dateString))
let dataStore = self.backendless.data.of(Contact().ofClass())
dataStore?.find(queryBuilder,
     response: {
         (result) -> () in
         let res = result as! [Contact]
         print("Result: \(res)")
     },
     error: {
         (fault : Fault?) -> () in
         print("Server reported an error: \(fault)")
     })
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
NSDateFormatter *dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = @"yyyyMMdd";
NSString *dateString = [dateFormatter stringFromDate:[[NSDate date] dateByAddingTimeInterval:-30*24*60*60]];
[queryBuilder setWhereClause:[NSString stringWithFormat:@"created > '%@'", dateString]];
id<IDataStore>dataStore = [backendless.data ofTable:@"Contact"];
[dataStore find:queryBuilder
    response:^(NSArray<NSDictionary<NSString *, id> *> *dataObjects) {
       NSLog(@"Result: %@", dataObjects);
    }
    error:^(Fault *fault) {
       NSLog(@"Server reported an error: %@", fault);
    }
];
let queryBuilder = DataQueryBuilder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
let dateString = dateFormatter.string(from: (NSDate() as Date).addingTimeInterval(-30*24*60*60))
queryBuilder!.setWhereClause(String(format: "created > '%@'", dateString))
let dataStore = self.backendless.data.ofTable("Contact")
dataStore?.find(queryBuilder,
                response: {
                    (result) -> () in
                    let res = result as! [[String : Any]]
                    print("Result: \(res)")
                },
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
                })

 

Data retrieval with Paging

Backendless operations returning a collection of objects automatically break up the complete result set into pages. Backendless paging uses two parameters to enable paged data retrieval:

page size - identifies how many objects a single page contains
offset - identifies the position of the object from which to retrieve the number of records identified by page size

 

The diagrams below illustrate the paging process. The first diagram shows the first request to retrieve a collection of records. The request includes the parameters of 5 objects in the page, starting from offset 0:

findrequest1

 

To retrieve the next page of data, the client must set the offset to the index of the first not retrieved object, which is 5:

findrequest2

 

Backendless server sets the maximum allowed value for the page size to 100 objects. The minimum value is 1.

 

iOS applications must use the DataQueryBuilder class to set the paging parameters. The example below configures DataQueryBuilder to load 25 objects starting from offset 50. The query is used to retrieve objects from the Person table:

DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[[queryBuilder setPageSize:25] setOffset:50];
id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore find:queryBuilder
           response:^(NSArray<Person *> *result) {
               NSLog(@"Result: %@", result);
           }
           error:^(Fault *fault) {
               NSLog(@"Server reported an error: %@", fault);
           }
     ];
let queryBuilder = DataQueryBuilder()
queryBuilder!.setPageSize(25).setOffset(50)
let dataStore = backendless.data.of(Person().ofClass())
dataStore?.find(queryBuilder,
                response: {
                   (result) -> () in
                   let res = result as! [Person]
                   print("Result: \(res)")
                },
                error: {
                   (fault : Fault?) -> () in
                   print("Server reported an error: \(fault)")
                })
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[[queryBuilder setPageSize:25] setOffset:50];
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore find:queryBuilder
           response:^(NSArray<NSDictionary<NSString *, id> *> *result) {
               NSLog(@"Result: %@", result);
           }
           error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
           }];
let queryBuilder = DataQueryBuilder()
queryBuilder!.setPageSize(25).setOffset(50)
let dataStore = backendless.data.ofTable("Person")
dataStore?.find(queryBuilder,
                response: {
                   (result) -> () in
                   let res = result as! [[String : Any]]
                   print("Result: \(res)")
                },
                error: {
                   (fault : Fault?) -> () in
                   print("Server reported an error: \(fault)")
                })

Application can use the same queryBuilder object to recalculate offset (and if necessary change page size) and use it to retrieve additional pages from the server using the same data retrieval method:

[queryBuilder prepareNextPage];
id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore find:queryBuilder
      response:^(NSArray<Person *> *result) {
          NSLog(@"Result: %@", result);
      }
      error:^(Fault *fault) {
          NSLog(@"Server reported an error: %@", fault);
      }];
queryBuilder?.prepareNextPage()
let dataStore = backendless.data.of(Contact().ofClass())
dataStore?.find(queryBuilder,
                response: {
                   (result) -> () in
                    let res = result as! [Contact]
                    print("Result: \(res)")
                },
                error: {
                    (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
                })
[queryBuilder prepareNextPage];
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore find:queryBuilder
      response:^(NSArray<NSDictionary<NSString *, id> *> *result) {
          NSLog(@"Result: %@", result);
      }
      error:^(Fault *fault) {
          NSLog(@"Server reported an error: %@", fault);
      }];
queryBuilder?.prepareNextPage()
let dataStore = backendless.data.ofTable("Person")
dataStore?.find(queryBuilder,
                response: {
                    (result) -> () in
                    let res = result as! [[String : Any]]
                    print("Result: \(res)")
                },
                error: {
                   (fault : Fault?) -> () in
                    print("Server reported an error: \(fault)")
                })

 

 

 

Sorting

Data retrieval and search API can request the server to return sorted data. Data can be sorted by one or more columns. The sorting direction can be either ascending (default) or descending for each individual column. iOS applications must use the DataQueryBuilder class to set the sorting option. The example below configures DataQueryBuilder to sort results by the name and age columns. Value in the age column are sorted in the descending order. The query is used to retrieve objects from the Person table:

    DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
    [queryBuilder setSortBy:@[@"name", @"age DESC"]];
    id<IDataStore>dataStore = [backendless.data of:[Contact class]];
    [dataStore find:queryBuilder
           response:^(NSArray<Contact *> *sorted) {
               NSLog(@"Sorted: %@", sorted);
           }
              error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
              }
     ];
        let queryBuilder = DataQueryBuilder()
        queryBuilder!.setSortBy(["name", "age DESC"])
        let dataStore = self.backendless.data.of(Contact().ofClass())        
        dataStore?.find(queryBuilder,
                        response: {
                            (sorted) -> () in
                            let sortedArr = sorted as! [Contact]
                            print("Result: \(sortedArr)")
        },
                        error: {
                            (fault : Fault?) -> () in
                            print("Server reported an error: \(fault)")
        })
    DataQueryBuilder *queryBuilder = [[DataQueryBuilder alloc] init];
    [queryBuilder setSortBy:@[@"name", @"age DESC"]];
    id<IDataStore>dataStore = [backendless.data ofTable:@"Contact"];
    [dataStore find:queryBuilder
           response:^(NSArray<NSDictionary<NSString *, id> *> *sorted) {
               NSLog(@"Sorted: %@", sorted);
           }
              error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
              }
     ];
        let queryBuilder = DataQueryBuilder()
        queryBuilder!.setSortBy(["name", "age DESC"])
        let dataStore = self.backendless.data.ofTable("Contact")
        dataStore?.find(queryBuilder,
                        response: {
                            (sorted) -> () in
                            let sortedArr = sorted as! [[String : Any]]
                            print("Result: \(sortedArr)")
        },
                        error: {
                            (fault : Fault?) -> () in
                            print("Server reported an error: \(fault)")
        })

 

The descending sorting order can be requested by adding DESC to the name of the column (separated by space).

Search by distance

With the ability to link data objects with geo points, you can search for data objects by distance. This type of search returns a paged set of data objects which satisfy the whereClause condition and located within the specified distance. Distance-based search uses a special function in whereClause of the search request. The syntax of the function is:

distance( 
   center point latitude, 
   center point longitude, 
   columnname which contains geo point.latitude,
   columnname which contains geo point.longitude )<operator> units-function(value)

where:

<operator> - Possible values are <, >, =, >=, <=
units-function - Defines the units of measure for the distance. Possible values are:
ft( X ) - the distance value X is expressed in feet
km( X ) - the distance value X is expressed in kilometers
mi( X ) - the distance value X is expressed in miles
yd( X ) -  the distance value X is expressed in yards

For example, the following whereClause expression searches for data objects located within 200 miles from the point at 30.26715, -97.74306. Each data object must have the "coordinates" property of type GeoPoint.

distance( 30.26715, -97.74306, coordinates.latitude, coordinates.longitude ) < mi(200)

The following example demonstrates a search-by-distance query. The example uses three data objects stored in the Friend table: Bob, Jane, and Fred who respectively live in Austin, Houston, San Antonio. The search query in the example finds all friends who live within the specified distance. Before running the search query, create the objects in the data storage with the corresponding geo points.

 

The application must be setup with demo data in order to run the example and see the distance operator in action. See the setup code in the expandable block below:

@interface Friend : NSObject
@property (nonatomic, strong) NSString *name; 
@property (nonatomic, strong) NSString *phoneNumber; 
@property (nonatomic, strong) GeoPoint *coordinates; 
@end
class Friend : NSObject {
    var name : String?
    var phoneNumber : String?
    var coordinates : GeoPoint?
}
The dictionary-based approach does not require a class definition for data objects saved in Backendless.
The dictionary-based approach does not require a class definition for data objects saved in Backendless.

 

Run the following query/code to store a data object representing Bob with a link to his home in Austin, TX:

id<IDataStore>dataStore = [backendless.data of:[Friend class]];

Friend *bob = [Friend new];
bob.name = @"Bob";
bob.phoneNumber = @"512-555-1212";
[dataStore save:bob
       response:^(Friend *friend) {
           NSLog(@"Bob object saved: %@", friend);
       }
       error:^(Fault *fault) {
           NSLog(@"Server reported an error: %@", fault);
       }];

GeoPoint *bobGeoPoint = [GeoPoint 
                         geoPoint:(GEO_POINT){.latitude = 30.26715, .longitude = -97.74306}
                         categories:@[@"Home"]
                         metadata:@{@"description":@"Bob's home"}];
                         
[backendless.geoService savePoint:bobGeoPoint
                         response:^(GeoPoint *geoPoint) {
                             NSLog(@"GeoPoint saved: %@", geoPoint);
                         }
                         error:^(Fault *fault) {
                             NSLog(@"Server reported an error: %@", fault);
                         }];

[dataStore setRelation:@"coordinates:GeoPoint:1"
        parentObjectId:@"Bob's objectId"
          childObjects:@[@"bobGeoPoint's objectId"]
              response:^(id result) {
                  NSLog(@"Relation saved");
              }
              error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
              }];
let dataStore = backendless.data.of(Friend().ofClass())

let bob = Friend ()
bob.name = "Bob"
bob.phoneNumber = "512-555-1212"
dataStore?.save(bob,
               response: {
                 (friend) -> () in
                 let fr = friend as! [Friend]
                 print("Friend saved: \(fr)")
               },
               error: {
                 (fault : Fault?) -> () in
                 print("Server reported an error: \(fault)")
               })
        
let bobGeoPoint = GeoPoint.geoPoint(
                     GEO_POINT(latitude: 30.26715, longitude: -97.74306),
                     categories: ["Home"],
                     metadata: ["description":"Bob's home"]) as! GeoPoint
        
backendless.geoService.save(bobGeoPoint,
                            response: {
                              (geoPoint : GeoPoint?) -> () in
                              print("GeoPoint saved: \(geoPoint)")
                            },
                            error: {
                              (fault : Fault?) -> () in
                              print("Server reported an error: \(fault)")
                            })
        
dataStore?.setRelation("coordinates:GeoPoint:1",
                       parentObjectId: "Bob's objectId",
                       childObjects: ["bobGeoPoint's objectId"],
                       response: {
                         (result) -> () in
                         print ("Relation saved")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                      })
id<IDataStore>dataStore = [backendless.data ofTable:@"Friend"];

NSMutableDictionary *bob = [NSMutableDictionary new];
[bob setValue:@"Bob" forKey:@"name"];
[bob setValue:@"512-555-1212" forKey:@"phoneNumber"];
[dataStore save:bob
              response:^(NSDictionary<NSString *, id> *friend) {
                  NSLog(@"Friend saved: %@", friend);
              }
                 error:^(Fault *fault) {
                     NSLog(@"Server reported an error: %@", fault);
                 }
];
  
GeoPoint *bobGeoPoint = [GeoPoint 
                          geoPoint:(GEO_POINT){.latitude = 30.26715, .longitude = -97.74306}
                          categories:@[@"Home"]
                          metadata:@{@"description":@"Bob's home"}];
                          
[backendless.geoService savePoint:bobGeoPoint
                         response:^(GeoPoint *geoPoint) {
                             NSLog(@"GeoPoint saved: %@", geoPoint);
                         }
                            error:^(Fault *fault) {
                              NSLog(@"Server reported an error: %@", fault);
                            }
];
    
[dataStore setRelation:@"coordinates:GeoPoint:1"
                      parentObjectId:@"Bob's objectId"
                        childObjects:@[@"bobGeoPoint's objectId"]
                            response:^(id result) {
                                NSLog(@"Relation saved");
                            }
                               error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault);
                               }
];
let dataStore = backendless.data.ofTable("Friend")

var bob = [String : Any]()
bob["name"] = "Bob"
bob["phoneNumber"] = "512-555-1212"
dataStore?.save(bob,
               response: {
                 (friend) -> () in
                 let fr = friend as! [String : Any]
                 print("Friend saved: \(fr)")
               },
               error: {
                 (fault : Fault?) -> () in
                 print("Server reported an error: \(fault)")
               })
        
let bobGeoPoint = GeoPoint.geoPoint(
                    GEO_POINT(latitude: 30.26715, longitude: -97.74306),
                    categories: ["Home"],
                    metadata: ["description":"Bob's home"]) as! GeoPoint
        
backendless.geoService.save(bobGeoPoint,
                            response: {
                              (geoPoint : GeoPoint?) -> () in
                              print("GeoPoint saved: \(geoPoint)")
                            },
                            error: {
                              (fault : Fault?) -> () in
                              print("Server reported an error: \(fault)")
                            })
        
dataStore?.setRelation("coordinates:GeoPoint:1",
                       parentObjectId: "Bob's objectId",
                       childObjects: ["bobGeoPoint's objectId"],
                       response: {
                         (result) -> () in
                         print ("Relation saved")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })

 

Run the following query/code to store a data object representing Jane with a link to her home in Houston, TX:

id<IDataStore>dataStore = [backendless.data of:[Friend class]];

Friend *jane = [Friend new];
jane.name = @"Jane";
jane.phoneNumber = @"281-555-1212";
[dataStore save:jane
                  response:^(Friend *friend) {
                     NSLog(@"Friend saved: %@", friend);
                  }
                  error:^(Fault *fault) {
                     NSLog(@"Server reported an error: %@", fault);
                  }];
    
GeoPoint *janeGeoPoint = [GeoPoint 
                             geoPoint:(GEO_POINT){.latitude = 29.76328, .longitude = -95.36327 }
                             categories:@[@"Home"]
                             metadata:@{@"description":@"Jane's home"}];
    
[backendless.geoService savePoint:janeGeoPoint
                             response:^(GeoPoint *geoPoint) {
                                 NSLog(@"GeoPoint saved: %@", geoPoint);
                             }
                             error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault);
                             }];

[dataStore setRelation:@"coordinates:GeoPoint:1"
            parentObjectId:@"Jane's objectId"
              childObjects:@[@"janeGeoPoint's objectId"]
                  response:^(id result) {
                      NSLog(@"Relation saved");
                  }
                     error:^(Fault *fault) {
                         NSLog(@"Server reported an error: %@", fault);
                     }];
let dataStore = backendless.data.of(Friend().ofClass())

let jane = Friend ()
jane.name = "Jane"
jane.phoneNumber = "281-555-1212"
dataStore?.save(jane,
                      response: {
                        (friend) -> () in
                        let fr = friend as! [Friend]
                        print("Friend saved: \(fr)")
                      },
                      error: {
                        (fault : Fault?) -> () in
                        print("Server reported an error: \(fault)")
                      })
        
let janeGeoPoint = GeoPoint.geoPoint(
                            GEO_POINT(latitude: 29.76328, longitude: -95.36327),
                            categories: ["Home"],
                            metadata: ["description":"Jane's home"]) as! GeoPoint
        
backendless.geoService.save(janeGeoPoint,
                            response: {
                              (geoPoint : GeoPoint?) -> () in
                              print("GeoPoint saved: \(geoPoint)")
                            },
                            error: {
                              (fault : Fault?) -> () in
                              print("Server reported an error: \(fault)")
                            })
        
dataStore?.setRelation("coordinates:GeoPoint:1",
                       parentObjectId: "Jane's objectId",
                       childObjects: ["janeGeoPoint's objectId"],
                       response: {
                         (result) -> () in
                         print ("Relation saved")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })
id<IDataStore>dataStore = [backendless.data ofTable:@"Friend"];

NSMutableDictionary *jane = [NSMutableDictionary new];
[jane setValue:@"Jane" forKey:@"name"];
[jane setValue:@"281-555-1212" forKey:@"phoneNumber"];
[dataStore save:jane
                  response:^(NSDictionary<NSString *, id> *friend) {
                      NSLog(@"Friend saved: %@", friend);
                  }
                     error:^(Fault *fault) {
                         NSLog(@"Server reported an error: %@", fault);
                     }];
    
GeoPoint *janeGeoPoint = [GeoPoint 
                              geoPoint:(GEO_POINT){.latitude = 29.76328, .longitude = -95.36327 }
                              categories:@[@"Home"]
                              metadata:@{@"description":@"Jane's home"}];

[backendless.geoService savePoint:janeGeoPoint
                             response:^(GeoPoint *geoPoint) {
                                 NSLog(@"GeoPoint saved: %@", geoPoint);
                             }
                                error:^(Fault *fault) {
                                    NSLog(@"Server reported an error: %@", fault);
                                }];

[dataStore setRelation:@"coordinates:GeoPoint:1"
            parentObjectId:@"Jane's objectId"
              childObjects:@[@"janeGeoPoint's objectId"]
                  response:^(id result) {
                      NSLog(@"Relation saved");
                  }
                     error:^(Fault *fault) {
                         NSLog(@"Server reported an error: %@", fault);
                     }
     ];
let dataStore = backendless.data.ofTable("Friend")

var jane = [String : Any]()
jane["name"] = "Jane"
jane["phoneNumber"] = "281-555-1212"
dataStore?.save(jane,
                response: {
                  (friend) -> () in
                  let fr = friend as! [String : Any]
                  print("Friend saved: \(fr)")
                },
                error: {
                  (fault : Fault?) -> () in
                  print("Server reported an error: \(fault)")
                })
        
let janeGeoPoint = GeoPoint.geoPoint(
                             GEO_POINT(latitude: 29.76328, longitude: -95.36327),
                             categories: ["Home"],
                             metadata: ["description":"Jane's home"]) as! GeoPoint
        
backendless.geoService.save(janeGeoPoint,
                               response: {
                                 (geoPoint : GeoPoint?) -> () in
                                 print("GeoPoint saved: \(geoPoint)")
                               },
                               error: {
                                 (fault : Fault?) -> () in
                                 print("Server reported an error: \(fault)")
                               })
        
dataStore?.setRelation("coordinates:GeoPoint:1",
                       parentObjectId: "Jane's objectId",
                       childObjects: ["janeGeoPoint's objectId"],
                       response: {
                         (result) -> () in
                         print ("Relation saved")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })

 

Run the following query/code to store a data object representing Fred with a link to his home in San Antonio, TX:

id<IDataStore>dataStore = [backendless.data of:[Friend class]];

Friend *fred = [Friend new];
fred.name = @"Fred";
fred.phoneNumber = @"210-555-1212";
[dataStore save:fred
                  response:^(Friend *friend) {
                      NSLog(@"Friend saved: %@", friend);
                  }
                     error:^(Fault *fault) {
                         NSLog(@"Server reported an error: %@", fault);
                     }];
    
GeoPoint *fredGeoPoint = [GeoPoint 
                          geoPoint:(GEO_POINT){.latitude = 29.42412, .longitude = -98.49363}
                          categories:@[@"Home"]
                          metadata:@{@"description":@"Fred's home"}];
    
[backendless.geoService savePoint:fredGeoPoint
                             response:^(GeoPoint *geoPoint) {
                                 NSLog(@"GeoPoint saved: %@", geoPoint);
                             }
                                error:^(Fault *fault) {
                                    NSLog(@"Server reported an error: %@", fault);
                                }];
                                
[dataStore setRelation:@"coordinates:GeoPoint:1"
            parentObjectId:@"Fred's objectId"
              childObjects:@[@"fredGeoPoint's objectId"]
                  response:^(id result) {
                      NSLog(@"Relation saved");
                  }
                     error:^(Fault *fault) {
                         NSLog(@"Server reported an error: %@", fault);
                     }];
let dataStore = backendless.data.of(Friend().ofClass())

let fred = Friend ()
fred.name = "Fred"
fred.phoneNumber = "210-555-1212"
dataStore?.save(fred,
                response: {
                  (friend) -> () in
                  let fr = friend as! [Friend]
                  print("Friend saved: \(fr)")
                },
                error: {
                  (fault : Fault?) -> () in
                  print("Server reported an error: \(fault)")
                })
        
let fredGeoPoint = GeoPoint.geoPoint(
                               GEO_POINT(latitude: 29.42412, longitude: -98.49363),
                               categories: ["Home"],
                               metadata: ["description":"Fred's home"]) as! GeoPoint
        
backendless.geoService.save(fredGeoPoint,
                                    response: {
                                        (geoPoint : GeoPoint?) -> () in
                                        print("GeoPoint saved: \(geoPoint)")
                                    },
                                    error: {
                                        (fault : Fault?) -> () in
                                        print("Server reported an error: \(fault)")
                                    })
        
dataStore?.setRelation("coordinates:GeoPoint:1",
                       parentObjectId: "Fred's objectId",
                       childObjects: ["fredGeoPoint's objectId"],
                       response: {
                         (result) -> () in
                         print ("Relation saved")                                
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })
id<IDataStore>dataStore = [backendless.data ofTable:@"Friend"];

NSMutableDictionary *fred = [NSMutableDictionary new];
[fred setValue:@"Fred" forKey:@"name"];
[fred setValue:@"210-555-1212" forKey:@"phoneNumber"];
[dataStore save:fred
                  response:^(NSDictionary<NSString *, id> *friend) {
                      NSLog(@"Friend saved: %@", friend);
                  }
                     error:^(Fault *fault) {
                         NSLog(@"Server reported an error: %@", fault);
                     }
     ];

GeoPoint *fredGeoPoint = [GeoPoint 
                          geoPoint:(GEO_POINT){.latitude = 29.42412, .longitude = -98.49363}
                          categories:@[@"Home"]
                          metadata:@{@"description":@"Fred's home"}];
    
[backendless.geoService savePoint:fredGeoPoint
                             response:^(GeoPoint *geoPoint) {
                                 NSLog(@"GeoPoint saved: %@", geoPoint);
                             }
                                error:^(Fault *fault) {
                                    NSLog(@"Server reported an error: %@", fault);
                                }];

[dataStore setRelation:@"coordinates:GeoPoint:1"
            parentObjectId:@"Fred's objectId"
              childObjects:@[@"fredGeoPoint's objectId"]
                  response:^(id result) {
                      NSLog(@"Relation saved");
                  }
                     error:^(Fault *fault) {
                         NSLog(@"Server reported an error: %@", fault);
                     }];
let dataStore = backendless.data.ofTable("Friend")

var fred = [String : Any]()
fred["name"] = "Fred"
fred["phoneNumber"] = "210-555-1212"
dataStore?.save(fred,
               response: {
                 (friend) -> () in
                 let fr = friend as! [String : Any]
                 print("Friend saved: \(fr)")
               },
               error: {
                 (fault : Fault?) -> () in
                 print("Server reported an error: \(fault)")
               })
               
let fredGeoPoint = GeoPoint.geoPoint(
                       GEO_POINT(latitude: 29.42412, longitude: -98.49363),
                       categories: ["Home"],
                       metadata: ["description":"Fred's home"]) as! GeoPoint
        
backendless.geoService.save(fredGeoPoint,
                            response: {
                              (geoPoint : GeoPoint?) -> () in
                              print("GeoPoint saved: \(geoPoint)")
                             },
                             error: {
                               (fault : Fault?) -> () in
                               print("Server reported an error: \(fault)")
                            })
        
dataStore?.setRelation("coordinates:GeoPoint:1",
                       parentObjectId: "Fred's objectId",
                       childObjects: ["fredGeoPoint's objectId"],
                       response: {
                         (result) -> () in
                         print ("Relation saved")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })

 

 

Once the data is in the persistent object and geo location storage, you can verify it in Backendless Console by opening the Geolocation screen and selecting the Home geocategory:

distance-search

 

Suppose you need to get all objects located within 300 miles radius from Beaumont, TX, which has the GPS coordinates of 30.084, -94.145. The following code/query performs that distance-based search:

DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:1];
[queryBuilder setWhereClause:@"distance( 30.26715, -97.74306, 
                                         Coordinates.latitude, 
                                         Coordinates.longitude ) < mi(200)"];
id<IDataStore>dataStore = [backendless.data of:[Friend class]];
[dataStore find:queryBuilder
           response:^(NSArray *friends) {
               for (Friend *friend in friends) {
                   NSLog(@"%@ lives at %@, %@ tagged as '%@'", friend.name,
                         friend.coordinates.latitude, friend.coordinates.longitude,
                         friend.coordinates.metadata[@"description"]);
               }
           }
           error:^(Fault *fault) {
               NSLog(@"Server reported an error: %@", fault);
           }];
let queryBuilder = DataQueryBuilder()
queryBuilder!.setRelationsDepth(1)
queryBuilder!.setWhereClause("distance( 30.26715, -97.74306, 
                                        Coordinates.latitude, 
                                        Coordinates.longitude ) < mi(200)")
let dataStore = backendless.data.of(Friend().ofClass())
dataStore?.find(queryBuilder,
                response: {
                  (friends) -> () in
                  for friend in friends as! [Friend] {
                  let info = friend.coordinates!.metadata["description"] as! String
                  print("\(friend.name) lives at \(friend.coordinates?.latitude), 
                         \(friend.coordinates?.longitude) tagged as '\(info)'")
                  }
                },
                error: {
                  (fault : Fault?) -> () in
                  print("Server reported an error: \(fault)")
                })
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:1];
[queryBuilder setWhereClause:@"distance( 30.26715, -97.74306, 
                                         Coordinates.latitude, 
                                         Coordinates.longitude ) < mi(200)"];
id<IDataStore>dataStore = [backendless.data ofTable:@"Friend"];
[dataStore find:queryBuilder
           response:^(NSArray *friends) {
               for (Friend *friend in frien              
             NSLog(@"%@ lives at the given coordinates", friend);
               }
           }
              error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
              }];
let queryBuilder = DataQueryBuilder()
queryBuilder!.setRelationsDepth(1)
queryBuilder!.setWhereClause("distance( 30.26715, -97.74306, 
                                        Coordinates.latitude, 
                                        Coordinates.longitude ) < mi(200)")
let dataStore = backendless.data.ofTable("Friend")
dataStore?.find(queryBuilder,
                response: {
                   (friends) -> () in
                   for friend in friends!  {
                     print("\(friend) lives at the given coordinates")
                  }
                },
                error: {
                  (fault : Fault?) -> () in
                  print("Server reported an error: \(fault)")
                })

The search returns all data objects within the specified distance. Each data object has the Coordinates property containing the coordinates of a geo point associated with this data object.

Relations Overview

A data object stored in Backendless may reference other objects from the same or other data tables. These references are called relations. When an object references a single object, it is a one-to-one relation. If a reference is for a collection of objects, it is a one-to-many relation. Relations can be established using either API or Backendless Console. When working with console, a relation in a table must be declared through a relation column. Console allows two types of relation columns: data object relationship and geopoint relationship. The former identifies a relation between two data tables, while the later declares a relationship between a data table and geopoint storage. It is not required to declare relation columns in console - Backendless can derive these relations from the API calls and subsequently creates columns in data tables.

 

The rest of this chapter describes relationship management using Backendless console. For the API reference, see the corresponding chapters.

Declaring a Relation Between Tables

Creating Relations Between Objects in Related Tables

Editing (Update/Delete) Relations

Deleting Relation Column

Declaring a Relation Between Tables

The instructions below describe how to declare a relation between two tables. As an example, the instructions below create a relation column address in the Person table. The new column will reference the Address table:

1. Select the Person table where a relation column should be declared.
2. Click the Schema menu.
3. Click the New button. The pop-up window will display as shown below:

adding-new-column.zoom80

4. Enter column name that will identify the relation.
5. Click the Type drop-down list and select the Data Object Relationship option.
6. Select a related table and the cardinality of the relations from the corresponding drop-down menus. The one-to-one relation means that a table's object can be linked with only one object from the related table. The one-to-many relation means that a table's object can be linked with more than one object from the related table.

relationship-column.zoom80

7. Click CREATE to save the changes.

Once a relationship column is declared, it will appear along other columns in the Data Browser view of the Backendless Console.

Creating Relations Between Objects in Related Tables

Once you have declared a relation between table schemas, you can establish a relation between objects in these tables. Follow the instructions below to link the objects:

1. Click the table name where you declared a relation. Console displays the columns representing relations slightly different than the other ones. The header for these columns includes:
- name of the related table;
- relation type (cardinality) visualized as either a single red line for one-to-one relations or three red lines for one-to-many relations;
- the "auto-load" checkbox.

relation-column-data-browser.zoom70

2. Every object in the table displays a plus icon on mouse hover which can be used to create a relation between that object and one from the related table:
plus-for-relation
3. Click the plus icon for the object to create a relation. The Set Related Object pop-up window will display the list of objects from the related table.

set-relation-popup.zoom70

4. Each object in the displayed popup has either a radio button or a checkbox next to the object's data. Radio buttons are used for one-to-one relations, while checkboxes apply for the one-to-many relations. Select the object(s) which will be linked to the parent object.
5. Click the ADD RELATION button to save the changes.
6. Once a relation between the objects is established, the related object(s) appear as hyperlinks which can be clicked to navigate to them:
relation-created

Editing (Update/Delete) Relations

You can edit the relations between the data objects. Editing a relation allows changing the related object or breaking the relation between the objects.

1. Click the name of the table containing the object with relations. Click the plus icon in the cell displaying the related object (the same plus icon you used to create a relation above):

editing-relation-plus-icon.zoom80

2. The Set Related Object pop-up window will open and display the related object. If you want to link a data object with other object(s), click the radio-button or check-box(s) next to the necessary object(s).
remove-relation.zoom70
3. Depending on the made changes, the action button at the bottom of the popup will say either REMOVE RELATION or UPDATE RELATION. Click the button to finalize the changes.

Deleting Relation Column

When a relation column is deleted, all references between the related objects are also removed. This operation does not delete the actual objects - it removes the column and any references between the related objects.

To delete a relation column using the Backendless Console:

1. Click the name of the table where you want to delete a relation column.
2. Click the Schema menu
3. Click the check-box next to the column you need to delete.
4. Click the Delete menu:

delete-relation-column

 

Relations API (Set/Add)

This and other relation API sections refer to objects in a relation as parent and child(ren). The parent object is the one that contains a reference to a related object. A child object is one that is being referenced by the parent.

There are two operations supporting creating a relation between objects - setting a relation and adding a relation:

Setting a relation - sets specified child objects as the related objects for the parent. If the parent had other child objects prior to the operation, the relation between them and the parent is removed. This is a replacement operation.
Adding a relation - this operation adds specified child objects to the collection of existing child objects. This is a concatenation operation. For one-to-one relations, if the parent object has a child for the specified column at the time when the operation is called, an error is returned back to the client

 

Other implementation details:

Both parent and child objects must exist in the Backendless storage.
If a column identifies a 1:1 relation which already exists between the parent and some other object, the "set" operation must replace the child object with the one identified in the operation. The "add" operation must return an error.
Add/set methods must return the number of objects added/set, that is, for example, if a whereClause is used and identifies an object (among others) which is already in the relation, that object must not be "accounted for" in the returned number of object.
If add/set receives a non-existing object, it must be ignored and the returned count of objects set/added must not reflect the ignored object.
Both operations create the relation column if it does not exist at the time of the method call.
 

There are two ways to add/set a relation:

Explicit relation list - an API request identifies specific objects to be added/set as children of the parent object. Available APIs:
Set Relation
Add Relation
Implicit relation list - an API request uses a query (the where clause) to identify objects which should be added/set as children of the parent object. Available APIs:
Set Relation
Add Relation

Set Relation with objects

API request replaces related objects with the ones identified in the API call. Child objects must be explicitly defined by referencing their IDs.

Synchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
NSNumber *result = [dataStore setRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                             childObjects:(NSArray *) children];
let dataStore = backendless.data.of(Person().ofClass())
let result = dataStore?.setRelation(columnName: String,
                        parentObjectId: String,
                          childObjects: [Any])

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore setRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
          childObjects:(NSArray *)children
              response:^(NSNumber *)responseBlock
                 error:^(Fault *)errorBlock];
let dataStore = backendless.data.of(Person().ofClass())
dataStore?.setRelation(columnName: String,
                   parentObjectId: String,
                     childObjects: [Any],
                         response: ((NSNumber?) -> Void) responseBlock,
                            error: ((Fault?) -> Void) errorBlock)

Synchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSNumber *result = [dataStore setRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                             childObjects:(NSArray *)children];
let dataStore = backendless.data.ofTable("TABLE-NAME")
let result = dataStore?.setRelation(columnName: String,
                        parentObjectId: String,
                          childObjects: [Any])

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
[dataStore setRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
          childObjects:(NSArray *)children
              response:^(NSNumber *)responseBlock
                 error:^(Fault *)errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")
dataStore?.setRelation(columnName: String,
                   parentObjectId: String,
                     childObjects: [Any],
                         response: ((NSNumber?) -> Void) responseBlock,
                            error: ((Fault?) -> Void) errorBlock)

where:

TABLE-NAME - name of the table where the parent object is stored.
YOUR-CLASS - Objective-C or Swift class of the parent object. The class name identifies the table where the parent object is stored.
parentObjectId - must be objectId of the object which will be assigned related children for columnName.
columnName - name of the column identifying the relation. Objects from the childObjects collection will be set as related for the  column in the table containing parentObjectId. The column name may optionally include table name separated by the colon character as well as cardinality which determines the type of relationship (one to one or one to many) (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "columnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

childObjects - a collection of child objectId values to set into the relation. Table of the child objects is identified by columnName. For the one-to-one relations the collection must contain one objectId value.

Return Value:

Number of child objects set into the relation.

Example:

The example below sets a relation for a one-to-one column named address declared in the Person table. The column must be declared in the table prior to the execution of the request shown below. This necessity is explained by missing table name qualifier in the columnName argument - notice the relation column is "address". If the argument value were "address:Address:1", then the column would be created automatically.
relation-column

 

id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore setRelation:@"address"
        parentObjectId:@"25E81497-3E17-13E1-FFE2-3729ACB7A900"
          childObjects:@[@"7D37E268-ECC0-24D0-FF9D-01F879577300"]
              response:^(NSNumber *result) {
                NSLog(@"Relation has been set: %@", result);
              }
              error:^(Fault *fault) {
                NSLog(@"Server reported an error: %@", fault);
              }];
let dataStore = backendless.data.of(Person().ofClass())
dataStore?.setRelation("address",
                       parentObjectId: "2670CC85-E654-E999-FFCD-54331ED22600",
                       childObjects: ["577B589F-CA32-3E56-FFB3-8C5A26D28700"],
                       response: {
                         (result : NSNumber?) -> () in
                         print ("Relation has been saved: \(result)")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore setRelation:@"address"
        parentObjectId:@"4E0B676C-27E4-1481-FF82-8512D68E7E00"
          childObjects:@[@"E3F8999E-D120-7B21-FF69-F0B24C6B1600"]
              response:^(NSNumber *result) {
                NSLog(@"Relation has been set: %@", result);
              }
              error:^(Fault *fault) {
                NSLog(@"Server reported an error: %@", fault);
              }];
let dataStore = backendless.data.ofTable("Person")
dataStore?.setRelation("address",
                       parentObjectId: "BA8BDFD2-316C-445B-FF45-C34CDA31BC00",
                       childObjects: ["E3F8999E-D120-7B21-FF69-F0B24C6B1600"],
                       response: {
                         (result : NSNumber?) -> () in
                         print ("Relation has been saved: \(result)")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })

 

Set Relation with condition

API request replaces related objects with the ones identified in the API call. Child objects are referenced implicitly - through a whereClause defining the condition for object selection.

Synchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
NSNumber *result = [dataStore setRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                              whereClause:(NSString *)whereClause];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
let result = dataStore.setRelation(columnName: String,
                               parentObjectId: String,
                               whereClause: String)

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore setRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
               whereClause:(NSString *)whereClause
                  response:^(NSNumber *)responseBlock
                     error:^(Fault *)errorBlock;
let dataStore = backendless.data.of(Person().ofClass())
dataStore.setRelation(columnName: String,
                  parentObjectId: String,
                     whereClause: String,
                     response: ((NSNumber?) -> Void)responseBlock,
                        error: ((Fault?) -> Void)errorBlock)

Synchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSNumber *result = [dataStore setRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                              whereClause:(NSString *)whereClause];
let dataStore = backendless.data.ofTable("TABLE-NAME")
let result = dataStore.setRelation(columnName: String,
                               parentObjectId: String,
                               whereClause: String)

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
[dataStore setRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
               whereClause:(NSString *)whereClause
                  response:^(NSNumber *)responseBlock
                     error:^(Fault *)errorBlock;
let dataStore = backendless.data.ofTable("TABLE-NAME")
dataStore.setRelation(columnName: String,
                  parentObjectId: String,
                     whereClause: String,
                        response: ((NSNumber?) -> Void)responseBlock,
                           error: ((Fault?) -> Void)errorBlock)

where:

TABLE-NAME - Name of the table where the parent object is stored.
YOUR-CLASS - Objective-C or Swift class of the parent object. The class name identifies the table where the parent object is stored.
parentObjectId - must be objectId of the object which will be assigned related children for columnName.
columnName - Name of the column identifying the relation. Objects identified by whereClause will be set as related for the  column in the table containing parentObjectId. The column name may optionally include table name separated by the colon character as well as cardinality which determines the type of relationship (one to one or one to many) (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "columnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

whereClause - a where clause condition identifying objects in the child table which will be set as the related objects for the parent object.

Return Value:

Number of child objects set into the relation.

Example:

The following request creates a relation between a Person object and all objects in the Users table which match the provided query. The query is specified in the whereClause argument:

name='Joe' or name = 'Frank'.

As a result of the operation, all User objects where the name property is either Joe or Frank will be set in the relation. The relation column is created if it does not exist. This is done because the column contains the child table qualifier, defined as ":Users:n" right after the column name.

id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore setRelation:@"users:Users:n"
        parentObjectId:@"41230622-DC4D-204F-FF5A-F893A0324800"
           whereClause:@"name='Joe' or name = 'Frank'"
              response:^(NSNumber *result) {
                NSLog(@"Relations has been set: %@", result);
              }
                 error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault);
                 }];
let dataStore = backendless.data.of(Person().ofClass())
dataStore?.setRelation("user:Users:n",
                        parentObjectId: "41230622-DC4D-204F-FF5A-F893A0324800",
                        whereClause: "name='Joe' or name = 'Frank'",
                        response: {
                          (result : NSNumber?) -> () in
                          print ("Relations has been saved: \(result)")
                        },
                        error: {
                          (fault : Fault?) -> () in
                          print("Server reported an error: \(fault)")
                        })
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore setRelation:@"users:Users:n"
        parentObjectId:@"41230622-DC4D-204F-FF5A-F893A0324800"
           whereClause:@"name='Joe' or name = 'Frank'"
              response:^(NSNumber *result) {
                NSLog(@"Relations has been set: %@", result);
              }
              error:^(Fault *fault) {
                NSLog(@"Server reported an error: %@", fault);
              }];
let dataStore = backendless.data.ofTable("Person")
dataStore?.setRelation("user:Users:n",
                        parentObjectId: "41230622-DC4D-204F-FF5A-F893A0324800",
                        whereClause: "name='Joe' or name = 'Frank'",
                        response: {
                          (result : NSNumber?) -> () in
                          print ("Relations has been saved: \(result)")
                        },
                        error: {
                          (fault : Fault?) -> () in
                          print("Server reported an error: \(fault)")
                        })

Add Relation with objects

This API request adds related objects to the existing collection. Child objects to add to the relation must be explicitly defined by referencing their IDs.

Synchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
NSNumber *result = [dataStore addRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                             childObjects:(NSArray *)children];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
let result = dataStore?.addRelation(columnName: String,
                                parentObjectId: String,
                                  childObjects: [Any])

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore addRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
          childObjects: (NSArray *)children
              response:^(id)responseBlock
                 error:^(Fault *)errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
dataStore?.addRelation(columnName: String,
                   parentObjectId: String,
                     childObjects: [Any],
                         response: ((Any?) -> Void),
                            error: ((Fault?) -> Void))

Synchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSNumber *result = [dataStore addRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                             childObjects:(NSArray *)children];
let dataStore = backendless.data.of("TABLE-NAME")
let result = dataStore?.addRelation(columnName: String,
                                parentObjectId: String,
                                  childObjects: [Any])

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
[dataStore addRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
          childObjects: (NSArray *)children
              response:^(id)responseBlock
                 error:^(Fault *)errorBlock];
let dataStore = backendless.data.of("TABLE-NAME")
dataStore?.addRelation(columnName: String,
                   parentObjectId: String,
                     childObjects: [Any],
                         response: ((Any?) -> Void),
                            error: ((Fault?) -> Void))

where:

TABLE-NAME - Name of the table where the parent object is stored.
YOUR-CLASS - Objective-C or Swift class of the parent object. The class name identifies the table where the parent object is stored.
parentObjectId - must be objectId of the object which will be assigned related children for columnName. .
relationColumnName - Name of the column identifying the relation. Objects from the childObjects collection will be added as related for the column in the table containing parentObjectId. The column name may optionally include table name separated by the colon character as well as cardinality which determines the type of relationship (one to one or one to many) (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "columnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

childObjects - a collection of child objectId values to set into the relation. Table of the child objects is identified by columnName.

Return Value:

Number of child objects added to the relation.

Example:

The example below adds a related object for a one-to-one column named address declared in the Person table. If the relation already contains an object, server returns an error since the addRelation API for one-to-one relations can add an object only once. The column must be declared in the table prior to the execution of the request shown below. This necessity is explained by missing table name qualifier in the columnName argument - notice the relation column name is "address". If it were specified as "address:Address", then the column would be created automatically.
relation-column
 

id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore addRelation:@"address:Address:1"
        parentObjectId:@"AFA8B1A5-21AF-B192-FF1D-D14676AB5200"
          childObjects:@[@"1E8DD8DB-D88C-B54A-FFB7-765C0E57C300"]
             response:^(NSNumber *result) {
               NSLog(@"Relation has been added: %@", result);
             }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }];
let dataStore = backendless.data.of(Person.ofClass())
dataStore?.addRelation("address:Address:1",
                 parentObjectId: "DE6F85D5-1981-1501-FFCF-B19D47964400",
                   childObjects: ["392D9664-4181-8F40-FF28-CF0A5699C900"],
                       response: {
                         (result : NSNumber?) -> () in
                          print ("Relation has been added: \(result)")
                       },
                       error: {
                          (fault : Fault?) -> () in
                           print("Server reported an error: \(fault)")
                       })
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore addRelation:@"address:Address:1"
        parentObjectId:@"A9226E7D-D04B-1ED7-FFA3-8A2671E39000"
          childObjects:@[@"7B3EB598-AED3-D40A-FF8D-0494A585EA00"]
             response:^(NSNumber *result) {
               NSLog(@"Relation has been added: %@", result);
             }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }];
let dataStore = backendless.data.ofTable("Person")
dataStore?.addRelation("address:Address:1",
                 parentObjectId: "DE6F85D5-1981-1501-FFCF-B19D47964400",
                   childObjects: ["392D9664-4181-8F40-FF28-CF0A5699C900"],
                       response: {
                         (result : NSNumber?) -> () in
                          print ("Relation has been added: \(result)")
                       },
                       error: {
                         (fault : Fault?) -> () in
                          print("Server reported an error: \(fault)")
                       })

Add Relation with condition

This API request adds related objects to the existing collection. Child objects to add to the relation are defined through a whereClause condition.

Synchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
NSNumber *result = [dataStore addRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                             whereClause:(NSString *)whereClause];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
let result = dataStore?.addRelation(columnName: String,
                                parentObjectId: String,
                                  whereClause: String)

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore addRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
          whereClause: (NSString *)whereClause
              response:^(id)responseBlock
                 error:^(Fault *)errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS().ofClass())
dataStore?.addRelation(columnName: String,
                   parentObjectId: String,
                      whereClause: String,
                         response: ((Any?) -> Void),
                            error: ((Fault?) -> Void))

Synchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSNumber *result = [dataStore addRelation:(NSString *)relationColumnName
                           parentObjectId:(NSString *)parentObjectId
                             whereClause:(NSString *)whereClause];
let dataStore = backendless.data.of("TABLE-NAME")
let result = dataStore?.addRelation(columnName: String,
                                parentObjectId: String,
                                  whereClause: String)

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore addRelation:(NSString *)relationColumnName
        parentObjectId:(NSString *)parentObjectId
          whereClause: (NSString *)whereClause
              response:^(id)responseBlock
                 error:^(Fault *)errorBlock];
let dataStore = backendless.data.of("TABLE-NAME")
dataStore?.addRelation(columnName: String,
                   parentObjectId: String,
                      whereClause: String,
                         response: ((Any?) -> Void),
                            error: ((Fault?) -> Void))

where:

TABLE-NAME - Name of the table where the parent object is stored.
YOUR-CLASS - Objective-C or Swift class of the parent object. The class name identifies the table where the parent object is stored.
parentObjectId - must be objectId of the object which will be assigned related children for columnName.
columnName - Name of the column identifying the relation. Objects identified by whereClause will be added as related for the column in parentObject. The column name may optionally include table name separated by the colon character as well as cardinality which determines the type of relationship (one to one or one to many) (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "relationColumnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

whereClause - a where clause condition identifying objects in the child table which will be added as related objects to the parent object.

Return Value:

Number of child objects added to the relation.

Example:

The following request adds objects from the Users table to a related property/column the Person object. Child objects added to the relation  must match the provided query. The query is specified in the whereClause argument:

name='Joe' or name = 'Frank'.

As a result of the operation, all User objects where the name property is either Joe or Frank will be added to the relation. The relation column is created if it does not exist. This is done because the column contains the child table qualifier, defined as ":Users:n" right after the column name.

id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore addRelation:@"address:Address:n"
        parentObjectId:@"B4752827-4D78-919A-FF67-609DE4D6DB00"
           whereClause:@"name='Joe' or name = 'Frank'"
             response:^(NSNumber *result) {
               NSLog(@"Relation has been added: %@", result);
             }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }];
let dataStore = backendless.data.of(Person.ofClass())   
dataStore?.addRelation("address:Address:n",
                 parentObjectId: "DABF9910-0B01-E424-FFE0-0C3F879F4D00",
                    whereClause: "name='Joe' or name = 'Frank'",
                       response: {
                         (result : NSNumber?) -> () in
                         print ("Relation has been added: \(result)")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore addRelation:@"address:Address:1"
        parentObjectId:@"B4752827-4D78-919A-FF67-609DE4D6DB00"
           whereClause:@"name='Joe' or name = 'Frank'"
              response:^(NSNumber *result) {
                NSLog(@"Relation has been added: %@", result);
              }
                 error:^(Fault *fault) {
                    NSLog(@"Server reported an error: %@", fault);
                 }
];
let dataStore = backendless.data.ofTable("Person")   
dataStore?.addRelation("address:Address:1",
                 parentObjectId: "DABF9910-0B01-E424-FFE0-0C3F879F4D00",
                    whereClause: "name='Joe' or name = 'Frank'",
                       response: {
                        (result : NSNumber?) -> () in
                         print ("Relation has been added: \(result)")
                       },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })

Relations API (Delete)

This and other relation API sections refer to objects in a relation as parent and child(ren). The parent object is the one that contains a reference to a related object. A child object is one that is being referenced by the parent.

Relation deletion API disconnects a relation between the parent object and some (or all) related children. The API does not delete any objects, it is responsible strictly for breaking the relationship. For object deletion API, see the Deleting Data Objects section of the documentation.

 

There are two ways to delete a relation:

Explicit deletion list - an API request identifies specific child objects to be disconnected from the parent object.

Implicit deletion list - an API request uses a condition (the where clause) to identify the child objects which should be disconnected from the parent object.

Delete Relation with objects

API request must identify the child objects to delete from the relation explicitly, by referencing their IDs.

Synchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
NSNumber *result = [dataStore deleteRelation:NSString *)relationColumnName
                              parentObjectId:(NSString *)parentObjectId
                                childObjects:(NSArray *)children];
let dataStore = backendless.data.ofTable("TABLE-NAME")        
let result = dataStore.deleteRelation(columnName: String,
                                   parentObjectId: String,
                                     childObjects: [Any])

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore deleteRelation:(NSString *)relationCplumnName
               parentObjectId:(NSString *)parentObjectId
                 childObjects:(NSArray *)children
                     response:^(NSNumber *)responseBlock
                        error:^(Fault *)errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS.ofClass())
let result = dataStore.deleteRelation(columnName: String,
                                  parentObjectId: String,
                                    childObjects: [Any],
                           response: ((NSNumber?) -> Void)responseBlock,
                                           error: ((Fault?) -> Void)errorBlock)

Synchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSNumber *result = [dataStore deleteRelation:NSString *)relationColumnName
                              parentObjectId:(NSString *)parentObjectId
                                childObjects:(NSArray *)children];
let dataStore = backendless.data.ofTable("TABLE-NAME")        
let result = dataStore.deleteRelation(columnName: String,
                                   parentObjectId: String,
                                     childObjects: [Any])

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore deleteRelation:(NSString *)relationCplumnName
               parentObjectId:(NSString *)parentObjectId
                 childObjects:(NSArray *)children
                     response:^(NSNumber *)responseBlock
                        error:^(Fault *)errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")   
let dataStore = backendless.data.of(YOUR-CLASS.ofClass())
let result = dataStore.deleteRelation(columnName: String,
                                  parentObjectId: String,
                                    childObjects: [Any],
                                        response: ((NSNumber?) -> Void)responseBlock,
                                           error: ((Fault?) -> Void)errorBlock)

where:

TABLE-NAME - Name of the table where the parent object is stored.
YOUR-CLASS - Objective-C or Swift class of the parent object. The class name identifies the table where the parent object is stored.
parentObjectId -must be objectId of the object which contains specified related child objects.
columnName - Name of the column identifying the relation. Relationship between the specified objects from the children collection will be deleted for the column in the table containing parentObjectId.
children - A collection of child objectId values for which the relationship with parentObjectId will be deleted.

Return Value:

Number of child objects for which the relationship has been deleted.

Example:

The example below deletes a relation between a Person object and its children. The child objects are referenced explicitly in the API call (see the object IDs in the collection as "XXXXX-XXXXX-XXXXX-XXXXX" and "ZZZZ-ZZZZ-ZZZZZ-ZZZZZ").The relation column is address.

id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore deleteRelation:@"address"
           parentObjectId:@"D8EE6A28-4F26-5CFD-FFD7-3BA9FA1B5100"
             childObjects:@[@"D5E69C74-E3BF-CB24-FFEC-2184AF390500"]
                 response:^(NSNumber *result) {
                   NSLog(@"Relation has been deleted: %@", result);
                 }
                    error:^(Fault *fault) {
                      NSLog(@"Server reported an error: %@", fault);
                    }
];
let dataStore = backendless.data.of(Person.ofClass())
dataStore?.deleteRelation("address",
                    parentObjectId: "B1B94EDF-CC19-AF54-FFD7-DBD2BF0F7000",
                      childObjects: ["4C4A98F3-493E-2247-FFB7-113AC8428E00"],
                          response: {
                            (result : NSNumber?) -> () in
                            print ("Relation has been deleted: \(result)")
                          },
                             error: {
                               (fault : Fault?) -> () in
                               print("Server reported an error: \(fault)")
                             })
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore deleteRelation:@"address"
           parentObjectId:@"1E621345-4ECD-8432-FFCC-15CF4FDF6900"
             childObjects:@[@"22A856F2-2BD2-C32B-FF2D-AA6BCF256A00"]
                 response:^(NSNumber *result) {
                   NSLog(@"Relation has been deleted: %@", result);
                 }
                   error:^(Fault *fault) {
                     NSLog(@"Server reported an error: %@", fault);
                   }
];
let dataStore = backendless.data.ofTable("Person")
dataStore?.deleteRelation("address",
                parentObjectId: "1E621345-4ECD-8432-FFCC-15CF4FDF6900",
                  childObjects: ["22A856F2-2BD2-C32B-FF2D-AA6BCF256A00"],
                      response: {
                        (result : NSNumber?) -> () in
                        print ("Relation has been deleted: \(result)")
                      },
                         error: {
                           (fault : Fault?) -> () in
                           print("Server reported an error: \(fault)")
                         })

Delete Relation with condition

API request must identify the child objects to delete from the relation implicitely through a whereClause condition.

Synchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
NSNumber *result = [dataStore deleteRelation:NSString *)relationColumnName
                              parentObjectId:(NSString *)parentObjectId
                                 whereClause:(NSString *)whereClause];
let dataStore = backendless.data.of(YOUR-CLASS.ofClass())        
let result = dataStore.deleteRelation(columnName: String,
                                   parentObjectId: String,
                                      whereClause: String)

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore deleteRelation:(NSString *)relationCplumnName
               parentObjectId:(NSString *)parentObjectId
                  whereClause:(NSString *)whereClause
                     response:^(NSNumber *)responseBlock
                        error:^(Fault *)errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS.ofClass())
let result = dataStore.deleteRelation(columnName: String,
                                  parentObjectId: String,
                                     whereClause: String,
                                        response: ((NSNumber?) -> Void)responseBlock,
                                           error: ((Fault?) -> Void)errorBlock)

Synchronous Method:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
NSNumber *result = [dataStore deleteRelation:NSString *)relationColumnName
                              parentObjectId:(NSString *)parentObjectId
                                 whereClause:(NSString *)whereClause];
let dataStore = backendless.data.ofTable("TABLE-NAME")        
let result = dataStore.deleteRelation(columnName: String,
                                  parentObjectId: String,
                                     whereClause: String)

Asynchronous Method:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
[dataStore deleteRelation:(NSString *)relationCplumnName
               parentObjectId:(NSString *)parentObjectId
                  whereClause:(NSString *)whereClause
                     response:^(NSNumber *)responseBlock
                        error:^(Fault *)errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")   
let dataStore = backendless.data.of(YOUR-CLASS.ofClass())
let result = dataStore.deleteRelation(columnName: String,
                                  parentObjectId: String,
                                     whereClause: String,
                                        response: ((NSNumber?) -> Void)responseBlock,
                                           error: ((Fault?) -> Void)errorBlock)

where:

TABLE-NAME - Name of the table where the parent object is stored.
YOUR-CLASS - Objective-C or Swift class of the parent object. The class name identifies the table where the parent object is stored.
parentObjectId - must be objectId of the object which contains related children identified by columnName.
columnName - Name of the column identifying the relation. Relationship between the child objects identified by whereClause will be deleted for this column in the table containing parentObjectId.
whereClause - a where clause condition identifying the objects in the child table which will be removed from the relation to the parent object.

Return Value:

Number of child objects for which the relationship has been deleted.

Example:

The following request deletes a relation between a Person object and all the related objects in the related table identified by column "user" which match the provided query:

name='Joe' or name = 'Frank'

As a result of the operation, all related objects where the name property is either Joe or Frank will be deleted from the relation.

id<IDataStore>dataStore = [backendless.data of:[Person class]];
[dataStore deleteRelation:@"user"
           parentObjectId:@"242B8013-E6C1-7AFB-FF86-DC607FAFC200"
              whereClause:@"name='Joe' or name = 'Frank'"
                 response:^(NSNumber *result) {
                   NSLog(@"Relation has been deleted: %@", result);
                 }
                    error:^(Fault *fault) {
                      NSLog(@"Server reported an error: %@", fault);
                    }
];
let dataStore = backendless.data.of(Person.ofClass())
dataStore?.deleteRelation("user",
                 parentObjectId: "669180E4-D982-62C0-FF50-94A7FD084C00",
                    whereClause: "name='Joe' or name = 'Frank'",
                       response: {
                         (result : NSNumber?) -> () in
                         print ("Relation has been deleted: \(result)")
                       },
                          error: {
                            (fault : Fault?) -> () in
                            print("Server reported an error: \(fault)")
                          })
id<IDataStore>dataStore = [backendless.data ofTable:@"Person"];
[dataStore deleteRelation:@"user"
           parentObjectId:@"5CEDF76B-1C02-9C74-FF07-288E59356400"
              whereClause:@"name='Joe' or name = 'Frank'"
                 response:^(NSNumber *result) {
                   NSLog(@"Relation has been deleted: %@", result);
                 }
                    error:^(Fault *fault) {
                      NSLog(@"Server reported an error: %@", fault);
                    }
];
let dataStore = backendless.data.ofTable("Person")
dataStore?.deleteRelation("user",
             parentObjectId: "5160318D-7449-A4F3-FFDD-601593A45500",
                whereClause: "name='Joe' or name = 'Frank'",
                   response: {
                     (result : NSNumber?) -> () in
                     print ("Relation has been deleted: \(result)")
                   },
                       error: {
                         (fault : Fault?) -> () in
                         print("Server reported an error: \(fault)")
                       })

Relations (Retrieve)

When a data object or a collection of objects is retrieved using the client API, Backendless does not automatically include related objects into the returned object hierarchy. The reason for this is it would make it very easy to load a lot of unnecessary data which could impact application's performance. There are multiple ways to retrieve related data:

Relations auto-load - a mechanism built-into Backendless Console for configuring specific relations to be included into responses with the parent objects.
Single-step relations retrieval - initializing and including related objects into the response when running a find query for parent objects.
Two-step relations retrieval - a process of retrieving relations where the first step is to load the parent object and the second step is to load specific relations for the given parent.
Loading with relations depth - retrieving related objects by specifying the depth of the object hierarchy
Relation paging API - enables "chunked" (or paged) retrieval of related objects
Inverted Retrieval - loading related child objects by condition applied to parent - load related objects using a search query where the condition applies to the parent object properties.

Auto Load

The auto-load feature is designed to be used for quick prototyping. One of its benefits is reduction of client-side code. However, for production quality apps, it is not recommended to use the auto-load relation retrieval as it can greatly impact performance.

By default when an object is retrieved from Backendless using any of the find APIs (basic or advanced), its related objects are not included into the response, unless explicitly referenced in the request. This behavior can be easily modified using Backendless Console:

auto-load

For any two tables A and B where A has a relationship column linking it to B, the console includes the "auto load" checkbox for the relationship column. Selecting the checkbox instructs Backendless to return a paged set of related B objects when the parent instance of A is retrieved through an API call. For example, in the image above, the shown table has a one-to-many "address" relationship with the Address table. When the "auto load" checkbox in the "address" column is selected, a paged set of the related Address objects will be included into the response for a find query for the shown table.

Single Step Retrieval

Single Step Retrieval loads only a partial set of the related objects (default size of the retrieved related collection is 10). To load additional related objects, use the Relation Paging API.

This approach allows retrieval of a partial set of the related objects along with the parent object in a single find request. Each relationship property (column) must be uniquely identified by name using the API documented below.

id<IDataStore> dataStore = // obtain a data store using dictionary or class-based approach

DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelated:@[@"RELATED-PROPERTY-NAME", 
                           @"RELATED-PROPERTY-NAME.RELATION-OF-RELATION"]];
[dataStore find:queryBuilder
       response:^(NSArray *result) {
           NSLog(@"Found objects: %@", result);
       }
          error:^(Fault *fault) {
              NSLog(@"Server reported an error: %@", fault);
          }
 ];
let dataStore = // obtain a data store using dictionary or class-based approach

let queryBuilder = DataQueryBuilder ()
queryBuilder!.setRelated(["RELATED-PROPERTY-NAME",
                          "RELATED-PROPERTY-NAME.RELATION-OF-RELATION"])
dataStore?.find(queryBuilder,
                response: {
                    (result : [Any]) -> () in
                    print("Found objects: \(result)")
                },
                error: {
                    (fault : Fault?) -> () in
                     print("Server reported an error: \(fault)")
                })

where

RELATED-PROPERTY-NAME - name of a related property to load. For example, if table Person has a relation "homeAddress" pointing to an object in the Address table, the value would be "homeAddress". The syntax allows to add relations of relations. For example, if the same Address table has a relation "country" pointing to the Country table, then "homeAddress.country" would instruct the related Country object to be loaded as well.

 

Two-step Retrieval

Two Step Retrieval loads only a partial set of the related objects (default size of the retrieved related collection is 10). To load additional related objects, use the Relation Paging API.

With this approach a collection of related objects for a specific relation property in a parent object is retrieved from the server. The client application must know parent object's objectId. The API loads a collection of related children for one property at a time. Child objects retrieved in paged sets, see the Relation Paging API sectionfor additional details.

 

Suppose the Person table has a one-to-many relationship column friends pointing to the Users table. The code below retrieves related BackendlessUser objects  for a specific Person object:

Prepare LoadRelationsQueryBuilder:
LoadRelationsQueryBuilder *loadRelationsQueryBuilder = [[LoadRelationsQueryBuilder alloc] initWithClass:[BackendlessUser class]];
[loadRelationsQueryBuilder setGetRelationName:@"friends"];
Synchronous call:
NSString *objectId = // removed for brevity
NSArray<BackendlessUser *> *friends = [dataStore loadRelations:objectId queryBuilder:loadRelationsQueryBuilder];
for (BackendlessUser *friend in friends) {
    NSLog(@"%@", [friend getEmail]);
}
Asynchronous call:
NSString *objectId = // removed for brevity
[dataStore loadRelations:objectId
            queryBuilder:loadRelationsQueryBuilder
                response:^(NSArray *friends) {
                    for (BackendlessUser *friend in friends) {
                        NSLog(@"%@", [friend getEmail]);
                    }
                }
                   error:^(Fault *fault) {
                       NSLog(@"Server reported an error: %@", fault);
                   }
];
Prepare LoadRelationsQueryBuilder:
let loadRelationsQueryBuilder = LoadRelationsQueryBuilder.init(with: BackendlessUser().ofClass())
loadRelationsQueryBuilder!.setGetRelationName("friends")
Synchronous call:
let objectId = // removed for brevity
let friends = dataStore?.loadRelations(objectId, queryBuilder: loadRelationsQueryBuilder) as! [BackendlessUser]
for friend in friends {
    print("\(friend.email)")
}
Asynchronous call:
let objectId = // removed for brevity
dataStore?.loadRelations(objectId,
                         queryBuilder: loadRelationsQueryBuilder,
                             response: {
                                (friends) -> () in
                                for friend in friends {
                                    print("\(friend.email)")
                                 }
                              },
                              error: {
                                (fault : Fault?) -> () in
                                 print("Server reported an error: \(fault)")
                              })

 

Retrieval with Relation Depth

The Data Service API supports a mechanism for loading related objects without identifying each by its name. Instead, the API includes a parameter which specifies the "depth" of the relations to include into the response. Consider the following diagram:

relation-depth

The diagram shows a hierarchy for class structure - the Order class has two relations: with OrderItem and Customer classes. Each in turn has a relation to the Manufacturer and Address classes. When an instance or a collection of Order objects is retrieved from Backendless, the API may include a parameter specifying the depth of relations to include into the response. If the relation depth is 1, then all related instances of OrderItem and Customer will be included into each Order object. If the relation depth is 2, then not only OrderItem and Customer instances will be included, but the corresponding Manufacturer and Address objects as well.

Loading relations with relation depth retrieves only a partial set of the related objects (default size of the retrieved related collection is 10). To load additional related objects, use the Relation Paging API.

API methods supporting relations depth

Synchronous Methods:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:(int)relationsDepth];

NSArray<YOUR-CLASS *> *result = [dataStore find:(DataQueryBuilder *)queryBuilder];

YOUR-CLASS *result = [dataStore findFirst:(DataQueryBuilder *)queryBuilder];

YOUR-CLASS *result = [dataStore findLast:(DataQueryBuilder *)queryBuilder];

YOUR-CLASS *result = [dataStore findById:(NSString *)objectId 
                      queryBuilder:(DataQueryBuilder *)queryBuilder];
let dataStore = backendless.data.of(YOUR-CLASS.ofClass())
let queryBuilder = DataQueryBuilder ()
queryBuilder.setRelationsDepth(relationsDepth: Int32)

let result = dataStore.find(queryBuilder: DataQueryBuilder) as! [YOUR-CLASS]

let result = dataStore.findFirst()(queryBuilder) as! YOUR-CLASS

let result = dataStore.findLast()(queryBuilder) as! YOUR-CLASS

let result = dataStore.find(byId: String, queryBuilder: DataQueryBuilder) as! YOUR-CLASS

Asynchronous Methods:

id<IDataStore>dataStore = [backendless.data of:[YOUR-CLASS class]];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:(int)relationsDepth];

[dataStore find:(DataQueryBuilder *)queryBuilder
       response:^(NSArray<YOUR-CLASS *> *result)responseBlock
          error:^(Fault *)errorBlock];

[dataStore findFirst:(DataQueryBuilder *)queryBuilder
       response:^(YOUR-CLASS *result)responseBlock
          error:^(Fault *)errorBlock];

[dataStore findLast:(DataQueryBuilder *)queryBuilder
       response:^(YOUR-CLASS *result)responseBlock
          error:^(Fault *)errorBlock];

[dataStore findById:(NSString *)objectId
           queryBuilder:(DataQueryBuilder *)queryBuilder
               response:^(YOUR-CLASS *result)responseBlock
                  error:^(Fault *)errorBlock];
let dataStore = backendless.data.of(YOUR-CLASS.ofClass())
let queryBuilder = DataQueryBuilder ()
queryBuilder.setRelationsDepth(relationsDepth: Int32)

dataStore.find(queryBuilder: DataQueryBuilder,
                response:(([YOUR-CLASS]) -> Void)resposeBlock,
                   error: ((Fault) -> Void)errorBlock)

dataStore.findFirst(queryBuilder: DataQueryBuilder,
                     response: ((YOUR-CLASS) -> Void)resposeBlock,
                        error: ((Fault) -> Void)errorBlock)

dataStore.findLast(queryBuilder: DataQueryBuilder,
                    response: ((YOUR-CLASS) -> Void)resposeBlock,
                       error: ((Fault) -> Void)errorBlock)

dataStore.find(byId: String,
                queryBuilder: DataQueryBuilder,
                 response: ((YOUR-CLASS) -> Void),
                    error: ((Fault) -> Void)

Synchronous Methods:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:(int)relationsDepth];

NSArray<NSDictionary<NSString *, id> *> *result = [dataStore find:(DataQueryBuilder *)queryBuilder];

<NSDictionary<NSString *, id> *result = [dataStore findFirst:(DataQueryBuilder *)queryBuilder];

<NSDictionary<NSString *, id> *result = [dataStore findLast:(DataQueryBuilder *)queryBuilder];

<NSDictionary<NSString *, id> *result = [dataStore findById:(NSString *)objectId 
                                               queryBuilder:(DataQueryBuilder *)queryBuilder];
let dataStore = backendless.data.ofTable("TABLE-NAME")
let queryBuilder = DataQueryBuilder ()
queryBuilder.setRelationsDepth(relationsDepth: Int32)

let result = dataStore.find(queryBuilder: DataQueryBuilder) as! [[String : Any]]

let result = dataStore.findFirst()(queryBuilder) as! [String : Any]

let result = dataStore.findLast()(queryBuilder) as! [String : Any]

let result = dataStore.find(byId: String, queryBuilder: DataQueryBuilder) as! [String : Any]

Asynchronous Methods:

id<IDataStore>dataStore = [backendless.data ofTable:@"TABLE-NAME"];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:(int)relationsDepth];

[dataStore find:(DataQueryBuilder *)queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *result)responseBlock
          error:^(Fault *)errorBlock];

[dataStore findFirst:(DataQueryBuilder *)queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *result)responseBlock
          error:^(Fault *)errorBlock];

[dataStore findLast:(DataQueryBuilder *)queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *result)responseBlock
          error:^(Fault *)errorBlock];

[dataStore findById:(NSString *)objectId
           queryBuilder:(DataQueryBuilder *)queryBuilder
              response:^(NSArray<NSDictionary<NSString *, id> *> *result)responseBlock
                  error:^(Fault *)errorBlock];
let dataStore = backendless.data.ofTable("TABLE-NAME")
let queryBuilder = DataQueryBuilder ()
queryBuilder.setRelationsDepth(relationsDepth: Int32)

dataStore.find(queryBuilder: DataQueryBuilder,
                response:(([[String : Any]]) -> Void)resposeBlock,
                   error: ((Fault) -> Void)errorBlock)

dataStore.findFirst(queryBuilder: DataQueryBuilder,
                     response: (([String : Any]) -> Void)resposeBlock,
                        error: ((Fault) -> Void)errorBlock)

dataStore.findLast(queryBuilder: DataQueryBuilder,
                    response: (([String : Any]) -> Void)resposeBlock,
                       error: ((Fault) -> Void)errorBlock)

dataStore.find(byId: String,
                queryBuilder: DataQueryBuilder,
                 response: (([String : Any]) -> Void),
                    error: ((Fault) -> Void)

Example:

id<IDataStore>dataStore = [backendless.data of:[Foo class]];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:2];
[dataStore find:queryBuilder
       response:^(NSArray<Foo *> *result) {
         NSLog(@"Found objects: %@", result);
       }
       error:^(Fault *fault) {
         NSLog(@"Server reported an error: %@", fault);
       }
];
let dataStore = backendless.data.of(Foo().ofClass())
let queryBuilder = DataQueryBuilder ()
queryBuilder!.setRelationsDepth(2)
dataStore?.find(queryBuilder,
                response: {
                  (result) -> () in
                  let res = result as! [Foo]
                  print("Found objects: \(res)")
                },
                error: {
                  (fault : Fault?) -> () in
                  print("Server reported an error: \(fault)")
                })
id<IDataStore>dataStore = [backendless.data ofTable:@"Foo"];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setRelationsDepth:2];    
[dataStore find:queryBuilder
       response:^(NSArray<NSDictionary<NSString *, id> *> *result) {
         NSLog(@"Found objects: %@", result);
       }
       error:^(Fault *fault) {
         NSLog(@"Server reported an error: %@", fault);
       }
];
let dataStore = backendless.data.ofTable("Foo")
let queryBuilder = DataQueryBuilder ()
queryBuilder!.setRelationsDepth(2)
dataStore?.find(queryBuilder,
                response: {
                  (result) -> () in
                  let res = result as! [[String : Any]]
                  print("Found objects: \(res)")
                },
                error: {
                  (fault : Fault?) -> () in
                  print("Server reported an error: \(fault)")
                })

 

Relation Paging

API operations described above provide a way to retrieve a limited (initial) set of related data. In most real world scenarios, there may be more related objects than returned by Single Step, Two Step or Relation Depth Retrieval APIs. To obtain a complete set of related objects Backendless supports Relation Paging API described below.

1. To understand the concept of paged data retrieval, see the Data retrieval with Paging section of the documentation.
2. When working with paging, it may be important to know the total size of the related collection. See the Get Object Count section for details on how to get the related objects count.

iOS applications must use the LoadRelationsQueryBuilder class which facilitates retrieval of related objects for a parent object. The class is responsible for maintaining the page size and offset parameters used in the paging process.

 

Before an instance of LoadRelationsQueryBuilder can be used in a data retrieval request, it must be initialized as shown below. Notice the setRelationName call which identifies a related column for which the related objects will be loaded:

TODO
TODO
TODO
TODO

where:

CHILDCLASS - Used in the "Custom Class" approach only. An Objective-C or Swift class which identifies the related table.
NAME-OF-RELATED-COLUMN - Name of the related column in the parent table for which the related objects will be retrieved.

 

Once a query builder object is created, the following API can be used to retrieve a page of related objects:

TODO
TODO
TODO
TODO

where:

parentObjectId -  id of the object for which the related objects will be retrieved.
PARENT-TABLE-NAME - name of the table which contains the parent object identified by parentObjectId.
queryBuilder - LoadRelationsQueryBuilder initialized as shown above. Used in subsequent calls to request additional pages of related data objects.
callback - a responder object which will receive a callback when a page of related objects is retrieved from the server. Applies to the asynchronous method only.

 

The LoadRelationsQueryBuilder class contains methods which recalculate offset in order to obtain next, previous or a specific page. After loadRelationsQueryBuilder is modified, call the API shown above to get next, previous or a specific page of data.

TODO
TODO

Inverted Relation Retrieval

Backendless supports a special query syntax for loading a subset of child objects for a specific parent. Consider the following table schemas:

 

PhoneBook table:

phonebook-table-schema

 

Contact table:

contact-table-schema

 

Address table:

address-table-schema

These tables will be used to demonstrate how to query Backendless for conditional related object retrieval.

Address Class:

@interface Address : NSObject

@property (nonatomic, strong) NSString *state;
@property (nonatomic, strong) NSString *city;
@property (nonatomic, strong) NSString *street;

@end
class Address: NSObject {    
    var state : String?
    var city : String?
    var street : String?
}

Contact Class:

@interface Contact : NSObject

@property (nonatomic, strong) Address *address;
@property int age;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *phone;
@property (nonatomic, strong) NSString *title;

@end
class Contact: NSObject {    
    var address : Address?
    var age = 0
    var name : String?
    var phone : String?
    var title : String?
}

PhoneBook Class:

@interface PhoneBook : NSObject
@property (nonatomic, strong) NSString *objectId;
@property (nonatomic, strong) NSArray<Contact *> *contacts;
@property (nonatomic, strong) Contact *owner;
@end
class PhoneBook: NSObject {
    var objectId : NSString!
    var contacts : [Contact]?
    var owner : Contact!
}

The dictionary-based approach does not require custom classes.

 

The general structure of a whereClause query to load a collection of child objects for a specific parent object is:

ParentTableName[ relatedColumnName ].parentColumnName COLUMN-VALUE-CONDITION

Both columns relatedColumnName and parentColumnName must be declared in a table with name of ParentTableName. The relatedColumnName must be a relation column. The table relatedColumnName points to is the table where the objects must be loaded from.  The examples below demonstrate the usage of this syntax:

Use the following code for all examples listed below. The only difference in the scenarios is the condition in whereClause.
NSString *whereClause = [NSString stringWithFormat:@"PUT THE WHERECLAUSE STATEMENT HERE", savedPhoneBook.objectId];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setWhereClause:whereClause];
[[backendless.data of:[Contact class]] find:queryBuilder
             response:^(NSArray<Contact *> *result) {
               NSLog(@"Found contacts: %@", result);
             }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }];
Find all contacts in a city for a specific phone book:
"PhoneBook[contacts].objectId = '%@' and address.city = 'Smallville'"
Find all contacts for the specific phone book where the city name contains letter 'a':
"PhoneBook[contacts].objectId = '%@' and address.city like '%%a%%'"
Find all contacts where age is greater than 20 for a specific phone book:
"PhoneBook[contacts].objectId = '%@' and age > 20"
Find all contacts for a specific phone book where age is within the specified range:
"PhoneBook[contacts].objectId = '%@' and age >= 21 and age <= 30"
Find all contacts for a specific phone book where age is greater than 20 and the city is Tokyo:
"PhoneBook[contacts].objectId = '%@' and age > 20 and address.city = 'Tokyo'"
Use the following code for all examples listed below. The only difference in the scenarios is the condition in whereClause.
let whereClause = "PUT THE WHERE CLAUSE STATEMENT HERE"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)
(backendless?.data.of(Contact().ofClass()).find(queryBuilder,
                   response: {
                     (result : [Any]?) -> () in
                     let res = result as! [Contact]
                     print("Contacts found:\(res)")
                   },
                   error: {
                     (fault : Fault?) -> () in
                     print("Server reported an error: \(fault)")
                   }))
Find all contacts in a city for a specific phone book:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook.objectId!)' and address.city = 'Smallville'"
Find all contacts for the specific phone book where the city name contains letter 'a':
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook.objectId!)' and address.city like '%a%'"
Find all contacts where age is greater than 20 for a specific phone book:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook.objectId!)' and age > 20"
Find all contacts for a specific phone book where age is within the specified range:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook.objectId!)' and age >= 21 and age <= 30"
Find all contacts for a specific phone book where age is greater than 20 and the city is Tokyo:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook.objectId!)' and age > 20 and address.city = 'Tokyo'"
Use the following code for all examples listed below. The only difference in the scenarios is the condition in whereClause.
NSString *whereClause = [NSString stringWithFormat:@"PUT THE WHERE CLAUSE STATEMENT HERE", [savedPhoneBook valueForKey:@"objectId"]];
DataQueryBuilder *queryBuilder = [DataQueryBuilder new];
[queryBuilder setWhereClause:whereClause];
[[backendless.data of:[Contact class]] find:queryBuilder
                   response:^(NSArray<NSDictionary<NSString *, id> *> *result) {
                     NSLog(@"Found contacts: %@", result);
                   }
                      error:^(Fault *fault) {
                        NSLog(@"Server reported an error: %@", fault);
                      }];
Find all contacts in a city for a specific phone book:
"PhoneBook[contacts].objectId = '%@' and address.city = 'Smallville'"
Find all contacts for the specific phone book where the city name contains letter 'a':
"PhoneBook[contacts].objectId = '%@' and address.city like '%%a%%'"
Find all contacts where age is greater than 20 for a specific phone book:
"PhoneBook[contacts].objectId = '%@' and age > 20"
Find all contacts for a specific phone book where age is within the specified range:
"PhoneBook[contacts].objectId = '%@' and age >= 21 and age <= 30"
Find all contacts for a specific phone book where age is greater than 20 and the city is Tokyo:
"PhoneBook[contacts].objectId = '%@' and age > 20 and address.city = 'Tokyo'"
Use the following code for all examples listed below. The only difference in the scenarios is the condition in whereClause.
let whereClause = "PUT THE WHERE CLAUSE STATEMENT HERE"
let queryBuilder = DataQueryBuilder()
queryBuilder!.setWhereClause(whereClause)
(backendless?.data.ofTable("Contact").find(queryBuilder,
              response: {
                (result : [Any]?) -> () in
                let res = result as! [[String : Any]]
                print("Contacts found:\(res)")
              },
              error: {
                (fault : Fault?) -> () in
                print("Server reported an error: \(fault)")
              }))
Find all contacts in a city for a specific phone book:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook["objectId"]!)' and address.city = 'Smallville'"
Find all contacts for the specific phone book where the city name contains letter 'a':
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook["objectId"]!)' and address.city like '%a%'"
Find all contacts where age is greater than 20 for a specific phone book:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook["objectId"]!)' and age > 20"
Find all contacts for a specific phone book where age is within the specified range:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook["objectId"]!)' and age >= 21 and age <= 30"
Find all contacts for a specific phone book where age is greater than 20 and the city is Tokyo:
let whereClause = "PhoneBook[contacts].objectId = '\(savedPhoneBook["objectId"]!)' and age > 20 and address.city = 'Tokyo'"

 

Relations with Geo Points

Backendless Geo Service manages application's geo location data and provides APIs to work with Geo points. Backendless supports integration between data objects managed by Data Service and geo points for the scenarios when a logical connection between the two entity types must exist in an application. For instance, in a taxi ordering application a data object may represent a taxi car, while a geo point represents its location on the map. Linking the two entity types together provides great benefits such as retrieving both objects at once and managing as a consistent, cohesive object hierarchy.

The Data-to-Geo integration is implemented through object relations. Data table schema may declare a table column with a special data type - "GeoPoint Relationship". As a result, the data objects in the table may contain a reference to one or more GeoPoints. When a data object with a related GeoPoint is saved, Backendless persists information about both the data object and the geo point in the corresponding persistent systems and sets up the relationship. Likewise, when a data object is retrieved by using the API, any related geo points can be retrieved using the same principle for loading data relations. The data-to-geo relation is bidirectional, it means a geo point may reference a data object in its metadata. You can learn more about it in the Relations with Data Objects section of the Geolocation documentation.

The relationship between a data object and a geo point (or a collection of) can be established by using either the "code first" or the "schema first" approaches. With the former, the relationship is determined by the data structure persisted with the API. If a data object references a GeoPoint (or a collection of) in one of its properties, Backendless interprets it as a relation and, as a result, will create a relation column in the data table schema. With the latter ("schema first") approach, application developer can declare a relationship in the data table schema first. In either one of these approaches, once a relationship is declared, data objects and geo points may be linked together by using the Backendless console as well.

 

This chapter consists of the following sections:

Declaring a Data-to-Geo Relationship in Table Schema

Linking a Data Object with Geo Points

Update/Delete Relations

Deleting Relation Column in Table Schema

Establishing Relations with Geo Points via API

Declaring a Data-to-Geo Relationship in Table Schema


To declare a relationship in a data table schema:

1. Select a table where a relation column should be declared.
2. Click the Schema menu.
3. Click the New button. Enter a name for the new column in the Name field. This column will represent a data-to-geo relationship.
4. Select the Geopoint Relationship option from the Type drop-down list.
geo-point-relationship-type
5. Select the constraints and/or cardinality of the relation from the corresponding drop-down menus.
 
Constraints:
There are two available constraints: "Not Null (Required)" and "Unique Value". The former (NN) establishes the column as required. It means when a new object is created or an existing one is updated, server will be expecting a value for the column. The latter constraint (UQ) will enforce a rule that the column must contain a unique value, thus no two objects will be able to contain the same GeoPoint.
 
Cardinality:
The one-to-one relation means that a table's object can be linked with only one geo point, while the one-to-many relation means that a table's object can be linked with multiple geo points.
location-column
6. Click the CREATE button to save the changes.

Linking a Data Object with Geo Points


Once a data-to-geo relationship column is declared, data objects from the table can be linked to geo point(s) as described below:

1. Click the name of the table containing an object you want to link with a geo point.
2. Table columns representing the data-to-geo relationships are identified as "GEOPOINT relationship" in the header row. The cardinality of the relation is visualized as one red line for the one-to-one relations and three red lines for the one-to-many relations:
geopoint-column.zoom70
3. To create a relationship between an object and a geopoint, click the plus icon next in the cell for a GEOPOINT column.
plus-icon-for-geopoint.zoom70
4. The Set Related GeoPoint pop-up window will display the list of the geo points. Use the Geo Category drop-down list to select a geo category from which the points should be displayed. Additionally, you can use the search bar to locate a geopoint by its metadata:
geopoint-search.zoom80
5. If you declared a one-to-one relation for a table the object belongs to, you will be able to link this object with only one geo point (by the means of a radio button). If it is a one-to-many relationship, the interface uses check boxes, which allow for multiple selection. Click a radio-button or select check-boxes next to the geo points which you want to link with the data object.
6. Click the ADD RELATION button to save the changes.

Once a relation is established, it is shown in the data browser as a hyperlink. The hyperlink for the one-to-one relations displays the coordinates of the related geo point. For the one-to-many relations the link says "multiple Geopoints". In both cases, the link opens the Geolocation screen of the console which displays the related geo point(s). Additionally, the link has a mouse-over preview for the related geopoints:

geopoint-preview

Update/Delete Relations


To update a data-to-geo relation, use the same plus icon that opened the popup to create a geopoint relation. The popup tracks the selection changes and allows update or deletion of the relation.

Deleting Relation Column in Table Schema


A data-to-geo relationship can be removed at the schema level. When a relationship column is removed, the "links" between the corresponding data objects and geopoints are deleted, however, it does not remove the objects themselves.

To delete a relationship definition between a data table and the geo points:

1. Click the name of the table which contains a "GeoPoint relationship" column you need to remove.
2. Click the Schema menu
3. Click the check-box next to the column you need to delete.
4. Click the Delete menu.
delete-geopoint-relation-column

Establishing Relations with Geo Points via API


Creating a relationship between a data object and a geo point (or a collection of) uses the same API as saving a data object with a related entity. In the case of data-to-geo relations, the related entity is a geopoint or a collection of geopoints. Consider the example that below saves a data object with a related geopoint. The geopoint is also persisted in the Geo Service:

The example below demonstrates how to link a data object representing a taxi with a geo point representing taxi's location.

 

First, declare the TaxiCab class:

@interface TaxiCab : NSObject

@property (nonatomic, strong) NSString *carmake;
@property (nonatomic, strong) NSString *carmodel;

@end
class TaxiCab: NSObject {
    var carmake : String?
    var carmodel : String?
}
The map-based approach does not require you defining any classes for the objects stored in Backendless. Instead your code can use NSDictionary (obj-c) or Dictionary (swift) or object maps to store and retrieve objects in/from Backendless.

 

To set one-to-one or one-to-may relation:

id<IDataStore>dataStore = [backendless.persistenceService of:[TaxiCab class]];
TaxiCab *taxi = [TaxiCab new];
taxi.carmake = @"Toyota";
taxi.carmodel = @"Prius";
[dataStore save:taxi];
    
// one-to-one relation between a data object and geo point
GeoPoint *point = [GeoPoint geoPoint:(GEO_POINT){.latitude = 40.7148, .longitude = -74.0059}
                              categories:@[@"taxi"]
                                metadata:@{@"service_area":@"NYC"}];
[backendless.geoService savePoint:point];
    
// link one point to data object
[dataStore setRelation:@"location:GeoPoint:1"
        parentObjectId:@"taxi's objectId"
          childObjects:@[@"point's objectId"]
              response:^(NSNumber *result) {
                NSLog(@"Relation saved");
              }
                 error:^(Fault *fault) {
                   NSLog(@"Server reported an error: %@", fault);
                 }
];

// one-to-many relation between a data object and geo points
GeoPoint *droppOff1 = [GeoPoint geoPoint:(GEO_POINT){.latitude = 40.757977, .longitude = -73.98557}
                              categories:@[@"DropOffs"]
                                metadata:@{@"name":@"Times Square"}];
[backendless.geoService savePoint:droppOff1];
    
GeoPoint *droppOff2 = [GeoPoint geoPoint:(GEO_POINT){.latitude = 40.748379, .longitude = -73.985565}
                              categories:@[@"DropOffs"]
                                metadata:@{@"name":@"Empire State Building"}];
[backendless.geoService savePoint:droppOff2];
    
// link several points to data object
[dataStore setRelation:@"DropOffs:GeoPoint:n"
        parentObjectId:@"taxi's objectId"
          childObjects:@[@"dropOff1 objectId", @"dropOff2 objectId"]
              response:^(NSNumber *result) {
                NSLog(@"Relations saved");
              }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }
];
let dataStore = backendless.data.of(TaxiCab().ofClass())!
let taxi = TaxiCab()
taxi.carmake = "Toyota"
taxi.carmodel = "Prius"
dataStore.save(taxi)
        
// one-to-one relation between a data object and geo point
let point = GeoPoint.geoPoint(GEO_POINT(latitude: 40.7148, longitude: -74.0059),
                                      categories: ["taxi"],
                                        metadata: ["service_area":"NYC"]) as! GeoPoint
backendless.geoService.save(point)
        
// link one point to data object
dataStore.setRelation("location:GeoPoint:1",
                 parentObjectId: "taxi's objectId",
                   childObjects: ["point's objectId"],
                       response: {
                         (result : NSNumber?) -> () in
                         print("Relation saved")
                       },
                          error: {
                            (fault : Fault?) -> () in
                            print("Server reported an error: \(fault)")
                       })
        
// one-to-many relation between a data object and geo points
let droppOff1 = GeoPoint.geoPoint(GEO_POINT(latitude: 40.757977, longitude: -73.98557),
                                  categories: ["DropOffs"],
                                    metadata: ["name":"Times Square"]) as! GeoPoint
backendless.geoService.save(droppOff1)
        
let droppOff2 = GeoPoint.geoPoint(GEO_POINT(latitude: 40.748379, longitude: -73.985565),
                                  categories: ["DropOffs"],
                                    metadata: ["name":"Empire State Building"]) as! GeoPoint
backendless.geoService.save(droppOff2)
        
// link several points to data object
dataStore.setRelation("DropOffs:GeoPoint:n",
                      parentObjectId: "taxi's objectId",
                        childObjects: ["dropOff1 objectId", "dropOff2 objectId"],
                            response: {
                              (result : NSNumber?) -> () in
                              print("Relations saved")
                            },
                               error: {
                                 (fault : Fault?) -> () in
                                 print("Server reported an error: \(fault)")
                               })
id<IDataStore>dataStore = [backendless.persistenceService ofTable:@"TaxiCab"];
NSDictionary* taxi = @{ @"carmake": @"Toyota", @"carmodel": @"Prius" };
[dataStore save:taxi];
    
// one-to-one relation between a data object and geo point
GeoPoint *point = [GeoPoint geoPoint:(GEO_POINT){.latitude = 40.7148, .longitude = -74.0059}
                            categories:@[@"taxi"]
                              metadata:@{@"service_area":@"NYC"}];
[backendless.geoService savePoint:point];
    
// link one point to data object
[dataStore setRelation:@"location:GeoPoint:1"
        parentObjectId:@"taxi's objectId"
          childObjects:@[@"point's objectId"]
              response:^(NSNumber *result) {
                NSLog(@"Relation saved");
              }
                error:^(Fault *fault) {
                  NSLog(@"Server reported an error: %@", fault);
                }
];
    
// one-to-many relation between a data object and geo points
GeoPoint *droppOff1 = [GeoPoint geoPoint:(GEO_POINT){.latitude = 40.757977, .longitude = -73.98557}
                              categories:@[@"DropOffs"]
                                metadata:@{@"name":@"Times Square"}];
[backendless.geoService savePoint:droppOff1];
    
GeoPoint *droppOff2 = [GeoPoint geoPoint:(GEO_POINT){.latitude = 40.748379, .longitude = -73.985565}
                              categories:@[@"DropOffs"]
                                metadata:@{@"name":@"Empire State Building"}];
[backendless.geoService savePoint:droppOff2];
    
// link several points to data object
[dataStore setRelation:@"DropOffs:GeoPoint:n"
         parentObjectId:@"taxi's objectId"
           childObjects:@[@"dropOff1 objectId", @"dropOff2 objectId"]
               response:^(NSNumber *result) {
                 NSLog(@"Relations saved");
               }
                  error:^(Fault *fault) {
                    NSLog(@"Server reported an error: %@", fault);
                  }
];
let dataStore = backendless.data.ofTable("TaxiCab")!
let taxi: [String: String] = ["carmake": "Toyota", "carmodel": "Prius"]
dataStore.save(taxi)
        
// one-to-one relation between a data object and geo point
let point = GeoPoint.geoPoint(GEO_POINT(latitude: 40.7148, longitude: -74.0059),
                              categories: ["taxi"],
                                metadata: ["service_area":"NYC"]) as! GeoPoint
backendless.geoService.save(point)
        
// link one point to data object
dataStore.setRelation("location:GeoPoint:1",
                      parentObjectId: "taxi's objectId",
                        childObjects: ["point's objectId"],
                            response: {
                              (result : NSNumber?) -> () in
                              print("Relation saved")
                            },
                              error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
                            })
        
// one-to-many relation between a data object and geo points
let droppOff1 = GeoPoint.geoPoint(GEO_POINT(latitude: 40.757977, longitude: -73.98557),
                                  categories: ["DropOffs"],
                                    metadata: ["name":"Times Square"]) as! GeoPoint
backendless.geoService.save(droppOff1)
        
let droppOff2 = GeoPoint.geoPoint(GEO_POINT(latitude: 40.748379, longitude: -73.985565),
                                  categories: ["DropOffs"],
                                    metadata: ["name":"Empire State Building"]) as! GeoPoint
backendless.geoService.save(droppOff2)
        
// link several points to data object
dataStore.setRelation("DropOffs:GeoPoint:n",
                      parentObjectId: "taxi's objectId",
                        childObjects: ["dropOff1 objectId", "dropOff2 objectId"],
                            response: {
                              (result : NSNumber?) -> () in
                              print("Relations saved")
                            },
                              error: {
                                (fault : Fault?) -> () in
                                print("Server reported an error: \(fault)")
                              })

 

After you run the code, you will see the following data object and geopoints created in the application:

Data object:

taxi-data-object.zoom70

Geopoints:

taxi-geo-points.zoom70

Data Security

Data Service supports a very flexible security mechanism for restricting access to objects stored in Backendless. Security permissions apply to users and roles. A permission can either grant or reject an operation for a particular asset. In the context of Data Service, the asset is an object which your app can retrieve, update or delete. Permissions can be granted or rejected globally, where they apply to all tables and all objects in the data store. Additionally, every table may have its own permission matrix and owner policy - a special instruction whether object owners can or cannot retrieve/update/delete the objects they 'own'. Finally, every object has its own Access Control List (ACL) which is a matrix of permissions for the operations applicable specifically to the object:

backendless-security-components

The security system is multi-layered. For any API call the system goes through several layers where each can trim the scope of the operations. The layered order of the decision making is important and consists of the following points of validation:

1. ObjectACL for the user who makes the call
2. ObjectACL for user-defined roles assigned to the user who makes the call.
3. Table permissions for the User account
4. Table permissions for the user-defined roles
5. Owner Policy
6. ObjectACL for system roles
7. Table permissions for system-level roles
8. Global user-defined roles
9. Global system roles

Where:

"User-defined roles" - roles created by the application developer.
"System roles" - roles built into Backendless (Authenticated User, NonAuthenticated User, SocialUser, etc).

 

Consider the following guide which illustrates the decision making process:

Backend receives an API request to load data from a table (the Find operation). For the sake of the example, user's identity is "bob@backendless.com" and the user belongs to a custom role called "MyRole".
 
All objects in the table become candidates for the retrieval. Backendless goes through the security permissions layers to determine which objects must be included into the response.
 

1. [LAYER 1] ObjectACL for the user who makes the call.
Backendless checks if there are any restrictions for the user account at the object level. Any object in the collection with ACL which rejects access to the user is excluded from the result. To see or modify the permissions for a particular object, click the 'lock' icon in the ACL column in the data browser in management console to see and manage Object ACL permissions for a specific object.
object-acl-access.zoom80
 
2. [LAYER 2] ObjectACL for user-defined roles assigned to the user who makes the call.
This is the same check as the one above, except Backendless looks into the permissions for the roles defined by the application developer. If the user who made the call belongs to any of the custom roles, Backendless checks if these roles are allowed to perform the current operation for every object in the collection. In the screenshot below, only the "MyRole" role will be checked in this step, since this is the only custom role in the application:
object-acl-custom-role.zoom80
 
3. [LAYER 3] Table permissions for the User account.
Every table in Backendless may have its own set of permissions for users and roles. At this point Backendless checks if the currently logged in user is allowed to run the current operation. For example, if the Find operation is denied for the user, no objects would be returned. To see the permissions for a user account in Backendless Console, select a table and click PERMISSIONS, then USER PERMISSIONS menu:
table-permissions-for-users.zoom80

 

4. [LAYER 4] Table permissions for the user-defined roles.
This step is identical to the one described above with the exception that is checks custom roles for the table. Since this guide reviews the decision making process for the Find operation, Backendless checks the column for Find. If any of the custom roles which the user belongs to deny access, the operation is rejected and no data is returned.
custom-user-roles-for-table.zoom80
 
5. [LAYER 5] Owner Policy.
When a new object is created in Backendless, the system automatically links it with the account of the user that made the call to save the object. You can see that information in the 'ownerId' column in any of your tables in the data browser.  With the association between objects and users (owners), Backendless provides a way to control whether users can get access to the data they created. This is done through a concept we call 'Owner Policy'. To navigate to Owner Policy, select a table in the data browser and click the PERMISSIONS menu. Click the OWNER POLICY menu item.
Granting a permission for an operation in Owner Policy, guarantees that the objects owned by the current user will be allowed access for the specified operations. Denying a permission, takes out the 'owned' objects from the collection of candidate objects to return.
 
6. [LAYER 6] Object ACL for system roles.
This check is identical to step 2 ([LAYER 2] Object ACL for custom roles). The difference is the system roles cover larger groups of users. For example, this step would make possible to restrict access to specific objects for all authenticated (or not authenticated) users, yet the object would be returned with a query made by the object's owner if the Owner Policy (previous step) grants access.
 
7. [LAYER 7] Table permissions for system roles.
Identical to step 4 (table permissions for custom roles), this checks if any of the system roles reject the operation at the table level.
 
8. [LAYER 8] Global custom roles.
Global policy applies to all tables and objects. By default all table level permissions inherit from the global policy. You can configure in the console at: Users > Security Roles. Create a new role and click it to configure the permission matrix:
custom-role-global-matrix.zoom70

Permissions API

Every data object in Backendless has its own access control list (ACL) - a matrix of operations and principals (application's users or roles). An intersection of an operation and a principal contains a permission which determines whether the principal has the right to execute the operation. These permission could be either grant or deny. Backendless console provides an easy to understand way to see and manage these permissions. For example, the screenshot below demonstrates an ACL matrix for an object. Notice the intersection of a column for the Create operation and the AuthenticatedUser role. The cell contains a green checkmark icon representing that the permission is granted:

permission-matrix

In addition to managing the ACL permissions with Backendless Console there is also Permissions API:

Synchronous Method Signatures:

Methods which throw an exception when server returns a fault:

// grants or denies a permission to a user to perform the operation on an object.
[backendless.data.permissions grantForUser:(NSString *)userId entity:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions grantForUser:(NSString *)userId entity:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions grantForUser:(NSString *)userId entity:(id)dataObject operation:DATA_UPDATE];
[backendless.data.permissions denyForUser:(NSString *)userId entity:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions denyForUser:(NSString *)userId entity:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions denyForUser:(NSString *)userId entity:(id)dataObject operation:DATA_UPDATE];

// grants or denies a permission to a role to perform the operation on an object.
[backendless.data.permissions grantForRole:(NSString *)roleName entity:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions grantForRole:(NSString *)roleName entity:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions grantForRole:(NSString *)roleName entity:(id)dataObject operation:DATA_UPDATE];
[backendless.data.permissions denyForRole:(NSString *)roleName entity:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions denyForRole:(NSString *)roleName entity:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions denyForRole:(NSString *)roleName entity:(id)dataObject operation:DATA_UPDATE];

// grants or denies a permission to all users to perform the operation on an object.
[backendless.data.permissions grantForAllUsers:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions grantForAllUsers:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions grantForAllUsers:(id)dataObject operation:DATA_UPDATE];
[backendless.data.permissions denyForAllRoles:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions denyForAllRoles:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions denyForAllRoles:(id)dataObject operation:DATA_UPDATE];

// grants or denies a permission to all roles to perform the operation on an object.
[backendless.data.permissions grantForAllRoles:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions grantForAllRoles:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions grantForAllRoles:(id)dataObject operation:DATA_UPDATE];
[backendless.data.permissions denyForAllRoles:(id)dataObject operation:DATA_FIND];
[backendless.data.permissions denyForAllRoles:(id)dataObject operation:DATA_REMOVE];
[backendless.data.permissions denyForAllRoles:(id)dataObject operation:DATA_UPDATE];
// grants or denies a permission to a user to perform the operation on an object.
backendless.data.permissions.grant(forUser: userId : String, entity: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.grant(forUser: userId : String, entity: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.grant(forUser: userId : String, entity: dataObject : Any, operation: DATA_UPDATE)
backendless.data.permissions.deny(forUser: userId : String, entity: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.deny(forUser: userId : String, entity: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.deny(forUser: userId : String, entity: dataObject : Any, operation: DATA_UPDATE)

// grants or denies a permission to a role to perform the operation on an object.
backendless.data.permissions.grant(forRole: roleName : String, entity: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.grant(forRole: roleName : String, entity: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.grant(forRole: roleName : String, entity: dataObject : Any, operation: DATA_UPDATE)
backendless.data.permissions.deny(forRole: roleName : String, entity: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.deny(forRole: roleName : String, entity: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.deny(forRole: roleName : String, entity: dataObject : Any, operation: DATA_UPDATE)

// grants or denies a permission to all users to perform the operation on an object.
backendless.data.permissions.grant(forAllUsers: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.grant(forAllUsers: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.grant(forAllUsers: dataObject : Any, operation: DATA_UPDATE)
backendless.data.permissions.deny(forAllUsers: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.deny(forAllUsers: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.deny(forAllUsers: dataObject : Any, operation: DATA_UPDATE)

// grants or denies a permission to all roles to perform the operation on an object.
backendless.data.permissions.grant(forAllRoles: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.grant(forAllRoles: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.grant(forAllRoles: dataObject : Any, operation: DATA_UPDATE)
backendless.data.permissions.deny(forAllRoles: dataObject : Any, operation: DATA_FIND)
backendless.data.permissions.deny(forAllRoles: dataObject : Any, operation: DATA_REMOVE)
backendless.data.permissions.deny(forAllRoles: dataObject : Any, operation: DATA_UPDATE)

Asynchronous Method Signatures:

Method which return result and fault through the responder object:

// grants or denies a permission to the user to perform the operation on the object.
[backendless.data.permissions grantForUser:(NSString *)userId
                                        entity:(id)dataObject
                                     operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                      response:^(id)responseBlock
                                         error:^(Fault *)errorBlock];
[backendless.data.permissions denyForUser:(NSString *)userId
                                        entity:(id)dataObject
                                     operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                      response:^(id)responseBlock
                                         error:^(Fault *)errorBlock];

// grants or denies a permission to a role to perform the operation on an object.
[backendless.data.permissions grantForRole:(NSString *) roleName
                                    entity:(id)dataObject
                                 operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                  response:^(id)responseBlock
                                     error:^(Fault *)errorBlock];
[backendless.data.permissions denyForRole:(NSString *) roleName
                                    entity:(id)dataObject
                                 operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                  response:^(id)responseBlock
                                     error:^(Fault *)errorBlock];

// grants or denies a permission to all users to perform the operation on an object.
[backendless.data.permissions grantForAllUsers:(id)dataObject
                                     operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                      response:^(id)responseBlock
                                         error:^(Fault *)errorBlock];
[backendless.data.permissions denyForAllUsers:(id)dataObject
                                     operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                      response:^(id)responseBlock
                                         error:^(Fault *)errorBlock];

// grants or denies a permission to all roles to perform the operation on an object.
[backendless.data.permissions grantForAllRoles:(id)dataObject
                                     operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                      response:^(id)responseBlock
                                         error:^(Fault *)errorBlock];
[backendless.data.permissions denyForAllRoles:(id)dataObject
                                     operation:DATA_FIND / DATA_REMOVE / DATA_UPDATE
                                      response:^(id)responseBlock
                                         error:^(Fault *)errorBlock];
// grants or denies a permission to a user to perform the operation on an object.
backendless.data.permissions.grant(forUser: userId : String,
            entity: dataObject : Any,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)
backendless.data.permissions.deny(forUser: userId : String,
            entity: dataObject : Any,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)

// grants or denies a permission to a role to perform the operation on an object.
backendless.data.permissions.grant(forRole: roleName : String,
            entity: dataObject,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)
backendless.data.permissions.deny(forRole: roleName : String,
            entity: dataObject,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)

// grants or denies a permission to all users to perform the operation on an object.
backendless.data.permissions.grant(forAllUsers: dataObject : Any,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)
backendless.data.permissions.deny(forAllUsers: dataObject : Any,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)

// grants or denies a permission to all roles to perform the operation on an object.
backendless.data.permissions.grant(forAllRoles: dataObject : Any,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)
backendless.data.permissions.deny(forAllRoles: dataObject : Any,
            operation: DATA_FIND / DATA_REMOVE / DATA_UPDATE,
            response: responseBlock : (Any) -> Void,
            error: errorBlock : (Fault) ->Void)

Example:

@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSNumber *age;
@end

- (void)permissionsMethod {
    BackendlessUser *user = [backendless.userService login:@"bob@foo.com" password:@"bob"];
    NSArray *roles = [backendless.userService getUserRoles];
    
    Person *person = [Person new];
    person.name = @"Jack";
    person.age = @(20);
    id <IDataStore> people = [backendless.data of:[Person class]];
    Person *savedPerson = [people save:person];
    
    [backendless.data.permissions grantForRole:roles[1]
                                        entity:savedPerson
                                     operation:DATA_UPDATE
                                      response:^(Person *newSavedPerson) {
                                          savedPerson.name = @"Tom";
                                          [people save:savedPerson];
                                      }
                                         error:^(Fault *fault) {
                                             NSLog(@"Server reported an error: %@", fault.description);
                                         }];
    
    
    [backendless.data.permissions denyForUser:user.objectId
                                       entity:savedPerson
                                    operation:DATA_UPDATE
                                     response:^(Person *newSavedPerson) {
                                       savedPerson.name = @"Tom";
                                       [people save:savedPerson];
                                     }
                                        error:^(Fault *fault) {
                                          NSLog(@"Server reported an error: %@", fault.description);
                                        }];
}
class Person: NSObject {    
    var name : NSString?
    var age : NSNumber?
}

func permissionsMethod () {
    let user : BackendlessUser = backendless.userService.login("bob@foo.com", password: "bob")
    let roles : [String] = backendless.userService.getUserRoles()
    
    let person : Person = Person ()
    person.name = "Jack";
    person.age = 20;
    let people = backendless.data.of(Person().ofClass())!
    let savedPerson = people.save(person) as! Person
    
    backendless.data.permissions.grant(forRole: roles[1],
                                       entity: savedPerson,
                                       operation: DATA_UPDATE,
                                       response: {
                                        (newSavedPerson : Any) -> Void in
                                        savedPerson.name = "Tom"
                                        people.save(savedPerson)
                                      },
                                       error: {
                                        (fault : Fault?) -> Void in
                                        print("Server reported an error: \(fault?.description)")
                                      })
    
    backendless.data.permissions.deny(forUser: user.objectId as String!,
                                      entity: savedPerson,
                                      operation: DATA_UPDATE,
                                      response: {
                                        (newSavedPerson : Any) -> Void in
                                        savedPerson.name = "Tom"
                                        people.save(savedPerson)
                                      },
                                      error: {
                                        (fault : Fault?) -> Void in
                                        print("Server reported an error: \(fault?.description)")
                                     })
}

Messaging API

Overview

Data Messaging is an essential function of mobile and desktop applications. It can be used for a multitude of functions including chat or private messaging, system update broadcast, maintaining game scores, etc. The Backendless Messaging Service provides API and software infrastructure enabling publish-subscribe message exchange pattern and mobile push notifications. The service consists of the following core concepts: channels, publishers, subscribers and registered devices:

channel        -  a logical medium "transporting" the messages.
publisher        - a program using the Publishing API to send messages to a channel.
subscriber        - a program using the Subscription API to receive messages from a channel.
registered device        - a mobile device registered with a Backendless channel to receive push notifications.

 

Publish-Subscribe Messaging

With the publish-subscribe pattern, one part of the code (or an entire application) can subscribe to receive messages and another publishes messages. A message can be any data - Backendless supports messages of primitive or complex data types. To enable publish-subscribe messaging, Backendless supports the concept of a channel. Subscriptions are "attached" to a channel (or multiple channels) and messages are published into a channel. By default Backendless sends all messages published into a channel to all the channel's subscribers. However, a subscription can include message filters, in this case Backendless delivers only the messages matching the filter.

 

Push Notifications

A message published to a channel can be tagged as a push notification, thus triggering the logic for distributing it to the registered devices. Backendless supports push notifications for iOS, Android and Windows Phone devices. Messages published as push notifications can target either a specific subscriber (as a device) or a group of subscribers. Subscribers can be grouped by operating system (for example, a message sent to all registered iOS devices) or as a collection of individual registrations. The Backendless messaging API supports different types of push notifications - badge updates, alerts, etc.

Sending Email

Backendless provides API for email delivery on behalf of your application. Before the API can be used, the Backendless backend must be configured with your own SMTP server information. This is an important requirement as the API will not work if the Manage > App Settings > Email Settings section in Backendless Console contains default values.

 

Configuration


To configure the backend:

 

1. Login to Backendless Console.
2. Select an app.
3. Click Manage, then scroll down to Email Settings on the App Settings screen.
4. Fill out the form with the SMTP server information
email-settings.zoom50

 
where:

SMTP Server - Hostname or public IP address of the server where the SMTP server is running.
Port - The port number the SMTP server accepts requests on.
From - The Name which will appear in the From field of the sent out emails.
User ID - The user id or email address for the SMTP server connection authentication
Password - The password for the SMTP server connection authentication.
Security - Choose between SSL or TLS connection.

 

Make sure to click Test before saving any configuration changes. The Reset button discards any unsaved changes.

 

Sending Email API


Delivers an email message using current server-side email settings to the recipient specified in the API call.

 

Blocking methods:

#define backendless [Backendless sharedInstance]

// Sends a text-based email message with the specified "subject" and the "messageBody".
// The email is delivered to the email addressses in the "recipients" collection.
-(id)[backendless.messaging sendTextEmail:(NSString *)subject 
                                          body:(NSString *)messageBody 
                                          to:(NSArray<NSString*> *)recipients];

// Sends an HTML-based email message with the specified "subject" and the "messageBody".
// The email is delivered to the email addressses in the "recipients" collection. 
-(id)[backendless.messaging sendHTMLEmail:(NSString *)subject 
                                           body:(NSString *)messageBody 
                                           to:(NSArray<NSString*> *)recipients];

// Sends an email message with may have different (text and html) representations controlled by the "bodyParts" object. 
// Message is sent with the specified "subject" and the "messageBody".
// The email is delivered to the email addressses in the "recipients" collection.
-(id)[backendless.messaging sendEmail:(NSString *)subject 
                                       body:(BodyParts *)bodyParts 
                                       to:(NSArray<NSString*> *)recipients];

// Sends an email message with may have different (text and html) representations controlled by the "bodyParts" object. 
// Message is sent with the specified "subject" and the "messageBody".
// Message also includes attachments. An attachment is a reference to a file residing in the Backendless Hosting file storage. The path is calculated from the root of the file system (as it is seen in File Service browser in Backendless console) without the leading slash.
// The email is delivered to the email addressses in the "recipients" collection.
-(id)[backendless.messaging sendEmail:(NSString *)subject 
                                      body:(BodyParts *)bodyParts 
                                      to:(NSArray<NSString*> *)recipients 
                                      attachment:(NSArray *)attachments];
let backendless = Backendless.sharedInstance()

// Sends a text-based email message with the specified "subject" and "body".
// The email is delivered to the email addressses in the "to" array.
func backendless.messaging.sendTextEmail(subject: String, body: String, to: [String]) -> Any

// Sends an HTML-based email message with the specified "subject" and "body".
// The email is delivered to the email addressses in the "to" array.
func backendless.messaging.sendHTMLEmail(subject: String, body: String, to: [String]) -> Any

Non-blocking methods:

#define backendless [Backendless sharedInstance]

// Sends a text-based email message with the specified "subject" and the "messageBody".
// The email is delivered to the email addressses in the "recipients" collection.
-(void)sendTextEmail:(NSString *)subject 
                      body:(NSString *)messageBody 
                      to:(NSArray<NSString*> *)recipients 
                      response:(void(^)(id))responseBlock 
                      error:(void(^)(Fault *))errorBlock;

// Sends an HTML-based email message with the specified "subject" and the "messageBody".
// The email is delivered to the email addressses in the "recipients" collection.
-(void)sendHTMLEmail:(NSString *)subject 
                      body:(NSString *)messageBody 
                      to:(NSArray<NSString*> *)recipients 
                      response:(void(^)(id))responseBlock 
                      error:(void(^)(Fault *))errorBlock;

// Sends an email message with may have different (text and html) representations controlled by the "bodyParts" object. 
// Message is sent with the specified "subject" and the "messageBody".
// The email is delivered to the email addressses in the "recipients" collection.
-(void)sendEmail:(NSString *)subject 
                  body:(BodyParts *)bodyParts 
                  to:(NSArray<NSString*> *)recipients 
                  response:(void(^)(id))responseBlock 
                  error:(void(^)(Fault *))errorBlock;

// Sends an email message with may have different (text and html) representations controlled by the "bodyParts" object. 
// Message is sent with the specified "subject" and the "messageBody". Message also includes attachments. 
// An attachment is a reference to a file residing in the Backendless Hosting file storage. The path is 
// calculated from the root of the file system (as it is seen in File Service browser in Backendless console) 
// without the leading slash. The email is delivered to the email addressses in the "recipients" collection.
-(void)sendEmail:(NSString *)subject 
                  body:(BodyParts *)bodyParts 
                  to:(NSArray<NSString*> *)recipients 
                  attachment:(NSArray *)attachments 
                  response:(void(^)(id))responseBlock 
                  error:(void(^)(Fault *))errorBlock;
let backendless = Backendless.sharedInstance()

// Sends a text-based email message with the specified "subject" and "body".
// The email is delivered to the email addressses in the "recipients" collection.
func backendless.messaging.sendTextEmail(subject: String, 
                                         body: String, 
                                         to: String, 
                                         response: (Any -> Void), 
                                         error: (Fault -> Void)) -> Void

// Sends an HTML-based email message with the specified "subject" and "body".
// The email is delivered to the email addressses in the "recipients" collection.
func backendless.messaging.sendHTMLEmail(subject: String, 
                                         body: String, 
                                         to: String, 
                                         response: (Any -> Void), 
                                         error: (Fault -> Void)) -> Void

where:

subject - email message subject.
messageBody/body - plain text or HTML body of the email message.
bodyParts - an instance of the BodyParts class which contains either plain text and/or HTML version of the message body.
recipients - a collection of email addressed to deliver the email message to.
attachments - an array of file paths for the file entries from the Backendless File Service. Referenced files will be attached to the email message. The path is calculated from the root of the file system (as it is seen in File Service browser in Backendless console) without the leading slash. For example, if file agreement.txt is located at /documents/legal/, then the path in the API call must be "documents/legal/agreement.txt".

Example:

    NSString *subject = @"Hello from Backendless! (Async call)";
    NSString *body = @"This is an email sent by asynchronous API call from a Backendless backend";
    NSString *recipient = @"james.bond@mi6.co.uk";
    [backendless.messagingService
     sendHTMLEmail:subject body:body to:@[recipient]
     response:^(id result) {
         NSLog(@"ASYNC: HTML email has been sent");
     }
     error:^(Fault *fault) {
         NSLog(@"Server reported an error: %@", fault);
     }];
        Types.tryblock({ () -> Void in

            let subject = "Hello from Backendless! (Sync call)"
            let body = "This is an email sent by synchronous API call from a Backendless backend"
            let recipient = "james.bond@mi6.co.uk"
            self.backendless.messagingService.sendHTMLEmail(subject, body:body, to:[recipient])
            print("SYNC: HTML email has been sent")
            },
            
            catchblock: { (exception) -> Void in
                print("Server reported an error: \(exception as! Fault)")
            }
        )

 

Push Notifications

Push Notification Setup (Android)

Backendless can deliver published messages as push notifications to Android devices. Additionally, Backendless Console can be used to publish push notifications. In order to deliver a push notification to Android, the backend must be configured with Google API Key:

2. Click the Get started. Pick a platform button.
server-key-step1
3. Click Enable services for my Android App:
server-key-step2
4. Select your Android app from the list. if the app is not in the list, enter the name of the app, it will be automatically registered in the Google Developer Console.
5. Enter Android package name of your app.
6. Click Continue to Choose and configure services.
server-key-step3
7. Click Cloud Messaging and then click Enable Google Cloud Messaging:
server-key-step4
8. Google generates API key and sender ID. Copy Server API Key:
generated-ids
9. Open Backendless Console and select your application.
10. Click Manage and scroll down to Mobile Settings.
11. Paste the Google API Key into corresponding field located under the Android Push Notifications label:
adding-android-key.zoom50
12. Click Save. At this point the backend is configured and is ready to publish push notifications to Android devices.

 

Google Project Number (Sender ID)

In your project you should register the device in order to receive or send push notifications. To accomplish this, do the following:

1. Return to Step 8 in the instructions above.
2. Copy Sender ID.
3. Use the Sender ID value in Backendless.Messaging.registerDevice(...) method as GCMSenderID argument. For example:
google-project-number-in-code

Push Notification Setup (iOS)

Setting up your backend to support Push Notifications for iOS requires a few steps, most of which are in Apple Developer Member Center and Keychain Access. There are a few configuration steps in XCode as well, see the Setting Up XCode for developing Apps with Push Notifications. The process consists of the following steps:

 

 

Creating App ID

1. First we are going to create an App ID for the mobile application which will receive Push Notifications. Login to Apple Developer Member Center. Click on "App IDs" in the "Identifiers" section. Use the plus sign "+" button to create a new ID:
create-app-id.zoom60
2. When prompted enter App ID Prefix. Make sure it is descriptive enough so you recognize it later when you return to the Member Center.
3. Select Explicit App ID in the "App ID Suffix" section and enter the same bundle ID which you will be using in the application:
appidsuffix.zoom95
4. In App Services select the services which the application will use and click "continue":
appservices.zoom95
5. Make sure that Push Notifications are enabled and click "submit". This will conclude the App ID creation for the app:
confirm-app-id-creation.zoom90

 

Creating Certificate Request

Push Notifications require a certificate which will be used on a device by the means of a provisioning profile. Also the same certificate (transformed to the Personal Information Exchange - .p12 format) will be used by Backendless to publish Push Notifications. If this makes little sense, do not worry, you will need to perform these steps only ones and then can move on to code and using the APIs.

1. In order to create a certificate a Certificate Signing Request (CSR) must be issued. To create a CSR, open Keychain Access and select Keychain Access >> Certificate Assistant >> Request a Certificate from the main menu:
certificaterequest.zoom80
2. Enter your email address and Common Name (leave the CA Email Address field empty), select "Saved to disk" and click "Continue":
CertificateRequestData
3. Select a directory where to save the file and click Save.

 

Generating an SSL Certificate

The CSR file created in the section above will be used to create an SSL Certificate. That certificate will then be used by Backendless to publish push notifications.

1. Return to Apple Developer Member Center and select "All" under "Certificates". Click the plus button "+" to add a new certificate:
membercenteraddcertificate.zoom60
2. Select certificate type - there are two options Development and Production. For now select "Apple Push Notification service SSL (Sandbox)":
push-cert-sandbox.zoom90
3. Select the App ID created earlier in these instructions:
SelectAppId
4. Next you will see the instructions for generating a CSR which you have already created by now. Click Continue to proceed to the next step.
5. Select the CSR file created and saved to the disk earlier and click Generate:
select-csr-file
6. The certificate is ready now, click "Download" to download it:
download-push-cert.zoom90
7. Add the certificate file to Keychain Access.
8. Open Keychain Access and locate the certificate in the "My Certificates" section:
locate-push-cert-keychain-access.zoom60
9. Right click on the certificate and select the Export option:
export-push-cert.zoom70
10. Save the certificate in the p12 format:
save-cert-p12-format
11. Enter a password for the certificate. Make sure to make a record of the password - you will need to use it later in the instructions when you submit the certificate to Backendless:
enter-password
12. Enter your Mac OS X account password to confirm the action. At this point you have a certificate for Push Notifications.

 

Configuring Backendless App/Backend with the Certificate

Since Backendless provides the actual server-side integration for delivering Push Notifications for your application, it needs to have access to the certificate you created above. The steps below provide the instructions for uploading the certificate into Backendless:

1. Login to Backendless Console and select an application which you will use on the server-side.
2. Click Manage > App Settings. Locate the Mobile Settings section and click the IOS menu.
3. Click the Add Certificate button and upload the .p12 certificate created earlier. Make sure to enter the same password you used when created the certificate. The Channels control lets you select the messaging channels which the certificate will be used with. If you intend to use only one certificate, click the Add channels checkbox.
add-ios-cert
4. Now your Backendless server is ready to publish Push Notifications.

XCode Setup

XCode must be configured with a provisioning profile. Follow the instructions below to set it up:

1. Login to Apple Developer Member Center and select "All" under Provisioning Profiles. Click the plus button "+" to create a new profile:
add-provisioning-profile.zoom60
2. Select the "iOS App Development" profile type:
ios-app-dev-profile-type.zoom90
3. Click Continue. On the next screen select the App ID which was created earlier:
select-app-id
4. Click Continue. Select the users/certificates which will be included into the profile.
5. Click Continue. Select the devices to include into the profile.
6. Click Continue. Enter a name to assign to the profile and click Generate:
generate-cert
7. Download the profile:
download-profile
8. Once the provisioning profile is downloaded, locate it in the file system and double click to install it in the iPhone Configuration Utility.
 
9. If your iOS application is going to use Apple Push Notifications (APN), you will want to use this push notification-enabled profile to deploy your application to your device. For that to happen, the application’s bundle identifier must match the profile’s App ID. In Xcode, select the project from the project navigator and the target from the editor area. Then, select the Info pane and locate the Bundle Identifier key. Change it to exactly match the App ID in the provisioning profile you just created. (This value is case-sensitive!)
ios_set_bundle_id
10. One more step and your client application will be ready to receive push notifications. In the Build Settings pane, locate the Code Signing Identity setting. Click the value column for this setting and select the Notified provisioning profile from the list. (There are a number of sub-items in this setting. Changing the top-level item should also change all of the sub-items. However, if you have previously set a sub-item’s values to something other than its parent’s setting, you may need to change it individually) Code Signing Identity setting looks like the one:
ios_code_signing
Now you can build and run the application on a device.
 

Device Registration

In order to receive push notifications from Backendless, a device must be registered using the API documented below. Device registration may optionally include a list of messaging channels and/or an expiration date/time when the registration should be canceled.

 

Push notifications can be published using either API or Backendless Console. When a push notification is published, it goes through a messaging channel. Channels provide a way to establish a level of filtering - devices registered with a channel will receive notifications published to the channel. However, there are other way to narrow down push notification delivery. For example, a push notification may be sent to a specific device or a group of devices.

 

If no channels are specified in the device registration call, Backendless registers the device with the default channel. If the device registration call references a non-existing channel, Backendless creates the channel and registers the device with it. Registration expiration is a point in time (expressed as a timestamp) when the device registration must expire. Backendless removes the device registration at the specified time and the device no longer receives published push notifications.

 

In order to receive push notifications on a mobile device, application running on the device must register with Backendless using the API call below:

The process of device registration consists of two steps:

1. Registering for push notifications with Apple Push Notification Service (APNS) and receiving a device token.
2. Registering the device token with Backendless.

 

Step1: Registering with APNS

To initiate the sequence and register for push notifications with APNS add the following code:

iOS 8-9:
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes: 
                                 (UIUserNotificationTypeAlert | 
                                 UIUserNotificationTypeBadge | 
                                 UIUserNotificationTypeSound) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
iOS 10:
AppDelegate.h
#import <UserNotifications/UserNotifications.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
AppDelegate.m
- (BOOL)application:(UIApplication *)application 
                      didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    center.delegate = self;
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | 
                                             UNAuthorizationOptionAlert | 
                                             UNAuthorizationOptionBadge) 
                         completionHandler:^(BOOL granted, NSError * _Nullable error){
        if(!error){
            [[UIApplication sharedApplication] registerForRemoteNotifications];
        }
    }];
    return YES;
}
To receive the device token from APNS and then register the device with Backendless, application must declare the following method in main application delegate:
- (void)application:(UIApplication *)application 
           didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
           
  // REGISTER DEVICE WITH BACKENDLESS HERE
}
iOS 8-9:
let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
UIApplication.shared.registerForRemoteNotifications()
iOS 10:
AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    func application(_ application: UIApplication, 
                     didFinishLaunchingWithOptions 
                     launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
            UIApplication.shared.registerForRemoteNotifications()
        }    
        return true
    }
}
To receive the device token from APNS and then register the device with Backendless, application must declare the following method in main application delegate:
func application(_ application: UIApplication,  
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  // REGISTER DEVICE WITH BACKENDLESS HERE
}       

Step 2: Registering with Backendless

The following APIs for token/device registration are available:

Blocking Methods:

// Register device token with Backendless. 
// Associate registration with the 'default' channel and no expiration.
- (NSString *)[backendless.messaging registerDevice:(NSData *)deviceToken];

// Register device token with Backendless. 
// Associate registration with the channels and no expiration.
- (NSString *)[backendless.messaging registerDevice:(NSData *)deviceToken 
                                           channels:(NSArray<NSString *> *)channels];

// Register device token with Backendless. 
// Associate registration with the 'default' channel and the expiration date.
- (NSString *)[backendless.messaging registerDevice:(NSData *)deviceToken 
                                         expiration:(NSDate *)expirationDate];

// Register device token with Backendless. 
// Associate registration with the channels and set the expiration date.
- (NSString *)[backendless.messaging registerDevice:(NSData *)deviceToken 
                                           channels:(NSArray<NSString *> *)channels 
                                         expiration:(NSDate *)expirationDate];
// Register device token with Backendless. 
// Associate registration with the 'default' channel and no expiration.
func backendless.messaging.registerDevice(deviceToken: Data) -> String


// Register device token with Backendless. 
// Associate registration with the channels and no expiration.
func backendless.messaging.registerDevice(deviceToken: Data, 
                                          channels: [String]) -> String

// Register device token with Backendless. 
// Associate registration with the 'default' channel and the expiration date.
func backendless.messaging.registerDevice(deviceToken: Data, 
                                          expiration: Date) -> String

// Register device token with Backendless. 
// Associate registration with the channels and the expiration date.
func backendless.messaging.registerDevice(deviceToken: Data, 
                                          channels: [String], 
                                          expiration: Date) -> String

Non-blocking Methods:

// Register device token with Backendless. 
// Associate registration with the 'default' channel and no expiration.
- (void)[backendless.messaging registerDevice:(NSData *)deviceToken 
                                     response:^(NSString *)responseBlock 
                                        error:^(Fault *)errorBlock];

// Register device token with Backendless. 
// Associate registration with the channels and no expiration.
- (void)[backendless.messaging registerDevice:(NSData *)deviceToken 
                                     channels:(NSArray<NSString *> *)channels 
                                     response:^(NSString *)responseBlock 
                                        error:^(Fault *)errorBlock];

// Register device token with Backendless. 
// Associate registration with the 'default' channel and the expiration date.
- (void)[backendless.messaging registerDevice:(NSData *)deviceToken 
                                   expiration:(NSDate *)expirationDate 
                                     response:^(NSString *)responseBlock 
                                        error:^(Fault *)errorBlock];

// Register device token with Backendless. 
// Associate registration with the channels and the expiration date.
- (void)[backendless.messaging registerDevice:(NSData *)deviceToken 
                                     channels:(NSArray<NSString *> *)channels 
                                   expiration:(NSDate *)expirationDate 
                                     response:^(NSString *)responseBlock 
                                        error:^(Fault *)errorBlock];
// Register device token with Backendless. 
// Associate registration with the 'default' channel and no expiration.
func backendless.messaging.registerDevice(deviceToken: Data, 
                                          response: ((String) -> Void), 
                                          error: ((Fault) -> Void)) -> Void

// Register device token with Backendless. 
// Associate registration with the channels and no expiration.
func backendless.messaging.registerDevice(deviceToken: Data, 
                                          channels: [String], 
                                          response: ((String) -> Void), 
                                          error: ((Fault) -> Void)) -> Void

// Register device token with Backendless. 
// Associate registration with the 'default' channel and the expiration date.
func backendless.messaging.registerDevice(deviceToken: Data, 
                                          expiration: Date, 
                                          response: ((String) -> Void), 
                                          error: ((Fault) -> Void)) -> Void

// Register device token with Backendless. 
// Associate registration with the channels and the expiration date.
func backendless.messaging.registerDevice(deviceToken: Data, 
                                          channels: [String], 
                                          expiration: Date, 
                                          response: ((String) -> Void), 
                                          error: ((Fault) -> Void)) -> Void

where:

deviceToken - device token obtained from Apple Push Notification Service;
expiration - a timestamp when the device registration should expire;
channels - channel (or a collection of channels) to receive messages from. For the method without the argument, the "Default" channel is used;

Errors:

 

The following errors may occur during the message publishing API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

Error Code

Description

5004

Invalid expiration date. The expiration date must be after the current time.

8000

Property value exceeds the length limit. Error message should contain additional details about the violating property.

Example:

The following methods demonstrate registering a device for push notifications. Methods must be added to the main application delegate class:

- (void)application:(UIApplication *)app 
               didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    [backendless.messaging registerDevice:deviceToken
                                 response:^(NSString *result) {
                                     NSLog(@"Device token = %@", deviceToken);
                                 }
                                    error:^(Fault *fault) {
                                        NSLog(@"Server reported an error: %@", fault.description);
                                    }];
}


- (void)application:(UIApplication *)app  
              didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"Failed to register from remote notifictions: %@", error);
}
func application(_ application: UIApplication, 
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    backendless.messaging.registerDevice(deviceToken,
                                         response: {
                                            (result : String?) -> Void in
                                            print("Device token = \(deviceToken)")
    },
                                         error: {
                                            (fault : Fault?) -> Void in
                                            print("Server reported an error: \(fault?.description)")
    })
}


func application(_ application: UIApplication, 
                 didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("Failed to register from remote notifictions: \(error)")
}

Retrieve Device Registration

Backendless server returns information about device registration with the API documented below. The input parameter of the API is deviceId - the value returned by the device registration API.

#define backendless [Backendless sharedInstance]

// blocking method:
- (DeviceRegistration *)[backendless.messagingService getRegistration:(NSString *)deviceId];

// non-blocking method:
- (void)[backendless.messagingService getRegistration:(NSString *)deviceId 
                                      response:(void(^)(DeviceRegistration *))responseBlock 
                                      error:(void(^)(Fault *))errorBlock];
let backendless = Backendless.sharedInstance()

// blocking method:
func backendless.messaging.getRegistration(deviceId: String) -> DeviceRegistration

// non-blocking method:
func backendless.messaging.getRegistration(deviceId: String, 
                                           response:((DeviceRegistration) -> Void), 
                                           error:((Fault?) -> Void))

where:

deviceId        - device Identifier;

 
Return value:

Device registration object - an instance of the DeviceRegistration class. Provides access to the following properties:

deviceToken - a token assigned by the push notification service provider (Google Cloud Messaging, Apple Push Notification Service, Microsoft Push Notification Service).
deviceId - a unique identification of the device registered to receive push notifications.
os - operating system identifier
osVersion - version of the operating system
channels - an array of Backendless messaging channels device is registered with.
expiration - a timestamp indicating when the device registration should expire.

Use the following API to retrieve device's deviceId:

#define backendless [Backendless sharedInstance]

DeviceRegistration *deviceRegistraton = [backendless.messaging currentDevice];
NSString *deviceId = deviceRegistraton.deviceId;
let backendless = Backendless.sharedInstance()

let deviceRegistration: DeviceRegistration = backendless.messaging.currentDevice()
let deviceId: String = deviceRegistration.deviceId

Errors:

The following errors may occur during the device registration API callThe following errors may occur during the device registration API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

Error Code

Description

5000

Unable to retrieve device registration - unknown device ID.

Examples:

#define backendless [Backendless sharedInstance]

- (void)getDeviceRegistration:(NSString *)deviceId {
    [backendless.messagingService
     getRegistration:deviceId
     response:^(DeviceRegistration *registration) {
         NSLog(@"Registration: %@", registration);
     }
     error:^(Fault *fault) {
         NSLog(@"Server reported an error: %@", fault.description);
     }];
}
let backendless = Backendless.sharedInstance()

func getDeviceRegistration(deviceId: String) {
   backendless.messaging.getRegistration(
       deviceId,
       response: {
           (registration: DeviceRegistration?) -> Void in
           print("Registrtion: \(registration)")
   },
       error: {
           (fault: Fault?) -> Void in
           print("Server reported an error: \(fault?.description)")
   })
}

Managing Registrations

Application developers can manage device registrations using the Backendless Console. To see the device registrations:

1. Login to Backendless Console and select an app where you would like to see the device registrations.
2. Click the Messaging icon and Devices menu.

devices-screen.zoom50

The table displays all the current device registrations. Using the interface, you can:

Search for device registrations. The search string applies to all columns
Remove device registrations. To remove, use the check boxes and then click the Delete button
Deliver push notifications to the selected devices.

Publish Push Notifications

Backendless supports push notification delivery to iOS, Android and Windows Phone devices. A device must be registered (see the Device Registration API) in order to receive a push notification. A push notification can be published via API or using the Backendless Console. The content of a push notification consists of a message body and the specialized message headers.

 

Publish a Push Notification from Console

To publish a push notification from Backendless console, navigate to the Messaging screen and click the Devices tab. Make sure there are registered devices, otherwise a push notification has nowhere to go.

1. Select the check boxes for the device(s) you would like to send a notification to. Select Android or iOS tab to enter push notification parameters.
2. Enter parameter values into the corresponding fields.
3. Click the Publish Message button to send a push notification.
messaging-screen-v4.zoom50

 

Publish Push Notification with API

Message publishing API supports the following scenarios:

 

Basic push notification - In the simplest form a push notification is sent to a channel and this to all the devices registered with the channel. The notification message must contain appropriate headers which control the appearance of the notification on the devices. Example.

 

Push notification delivered to devices grouped by the OS - With this type of message delivery push notifications can be sent to all devices of a specific operating system (or a group of).  This includes delivery to Android, iOS or Windows Phone devices or a any combination of. Example.
 
Push notification delivered to specific devices - This delivery type allows push notification delivery to individual devices. Example
 
Delayed publishing - For all scenarios described above a push notification request can be configured to be executed at a later time (thus "delayed publishing"). Messages published with this delivery option can be canceled at any time using the message cancellation API. Example.

 

Push Notification Headers

The headers described below must be added to the "publish options" object or in case of REST clients, they are plain message headers:

Operating System

Headers

Description

iOS

"ios-alert":value

Sets the text of the alert message. If the header is not present and the published notification targets the iOS devices, Backendless sets the header to the value of the "message" argument. To disable this behavior, set the ios-alert header to null.

"ios-badge":value

Sets the value to update the badge with

"ios-sound":URL string or array of bytes

Sets either a URL for the sound notification to play on the device or an array of bytes for the sound to play.

Android

"android-ticker-text":value

Sets the text of the ticker showing up at the top of a device's screen when the device receives the notification.

"android-content-title":value

Sets the title of the notification as it is visible in the Android Notification Center

"android-content-text":value

Sets the message of the notification which appears under android-content-title in the Android Notification Center.

Windows Phone

"wp-title":value,
"wp-content":value

Sets the title and the content of a toast notification.

"wp-type":"TILE":
"wp-title" : value,
"wp-backgroundImage" : URL string,
"wp-badge" : number value,
"wp-backTitle" : value,
"wp-backImage" : URL string,
"wp-backContent" : value

Sets the properties for a tile notification.

"wp-type":"RAW",
"wp-raw":XMLString

Sets the properties for a raw notification

Publishes a push notification to a channel. Headers must be added to the publishOptions argument. The deliveryOptions argument can be used to:

1. Designate the message as "push notification only" or both a push notification and a pub/sub message.
2. Singlecast publishing - set a list of devices which should be receiving the notification.
3. Broadcast publishing - identify operating systems of the registered devices which should be receiving the notification.
4. Publish the message at a later time (delayed publishing).
5. Specify that the message should be republished multiple times (repeated publishing).

// These methods publish a message to the specified channel with publish options.
// Use these methods to send additional message headers (including specialized
// headers for push notifications), publisher ID and channel's subtopic.
// The only difference between the methods is sync vs. async and how
// they deliver return value or faults.

#define backendless [Backendless sharedInstance]

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
-(MessageStatus *)[backendless.messaging publish:(NSString *)channelName 
                                                  message:(id)message];

-(MessageStatus *)[backendless.messaging publish:(NSString *)channelName 
                                                  message:(id)message 
                                                  publishOptions:(PublishOptions *)publishOptions];

-(MessageStatus *)[backendless.messaging publish:(NSString *)channelName 
                                                  message:(id)message 
                                                  deliveryOptions:(DeliveryOptions *)deliveryOptions];

-(MessageStatus *)[backendless.messaging publish:(NSString *)channelName 
                                                  message:(id)message 
                                                  publishOptions:(PublishOptions *)publishOptions 
                                                  deliveryOptions:(DeliveryOptions *)deliveryOptions];
                           
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
-(void)[backendless.messaging publish:(NSString *)channelName 
                                       message:(id)message 
                                       response:(void(^)(MessageStatus *))responseBlock 
                                       error:(void(^)(Fault *))errorBlock];

-(void)[backendless.messaging publish:(NSString *)channelName 
                                       message:(id)message 
                                       publishOptions:(PublishOptions *)publishOptions 
                                       response:(void(^)(MessageStatus *))responseBlock 
                                       error:(void(^)(Fault *))errorBlock];

-(void)[backendless.messaging publish:(NSString *)channelName 
                                       message:(id)message 
                                       deliveryOptions:(DeliveryOptions *)deliveryOptions 
                                       response:(void(^)(MessageStatus *))responseBlock 
                                       error:(void(^)(Fault *))errorBlock];

-(void)[backendless.messaging publish:(NSString *)channelName 
                                       message:(id)message 
                                       publishOptions:(PublishOptions *)publishOptions 
                                       deliveryOptions:(DeliveryOptions *)deliveryOptions 
                                       response:(void(^)(MessageStatus *))responseBlock 
                                       error:(void(^)(Fault *))errorBlock];
// These methods publish a message to the specified channel with publish options.
// Use these methods to send additional message headers (including specialized
// headers for push notifications), publisher ID and channel's subtopic.

let backendless = Backendless.sharedInstance()

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
func backendless.messaging.publish(channelName: String, 
                                   message: Any) -> MessageStatus

func backendless.messaging.publish(channelName: String, 
                                   message: Any, 
                                   publishOptions: PublishOptions) -> MessageStatus

func backendless.messaging.publish(channelName: String, 
                                   message: Any, 
                                   deliveryOptions: DeliveryOptions) -> MessageStatus

func backendless.messaging.publish(channelName: String, 
                                   message: Any, 
                                   publishOptions: PublishOptions, 
                                   deliveryOptions: DeliveryOptions) -> MessageStatus
             
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
func backendless.messaging.publish(channelName: String, 
                                   message: Any, 
                                   response: ((MessageStatus) -> Void), 
                                   error: ((Fault) -> Void))

func backendless.messaging.publish(channelName: String, 
                                   message: Any, 
                                   publishOptions: PublishOptions, 
                                   response: ((MessageStatus) -> Void), 
                                   error: ((Fault) -> Void))

func backendless.messaging.publish(channelName: String, 
                                   message: Any, 
                                   deliveryOptions: DeliveryOptions, 
                                   response: ((MessageStatus) -> Void), 
                                   error: ((Fault) -> Void))
                                   
func backendless.messaging.publish(channelName: String, 
                                   message: Any, 
                                   publishOptions: PublishOptions, 
                                   deliveryOptions: DeliveryOptions, 
                                   response: ((MessageStatus) -> Void), 
                                   error: ((Fault) -> Void))

Return value:

MessageStatus        - a data structure with the assign message ID which can be used to obtain push notification status

Errors:

The following errors may occur during the message publishing API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

Error Code

Description

5003

Invalid repeatExpiresAt date in delivery options.

5007

User does not have the permission to publish messages

5030

Invalid publishAt date in the delivery options.

Examples:

Basic push notification

Targeting a group of devices (grouped by OS)

Targeting specific devices

Delayed publishing

Basic push notification

#define backendless [Backendless sharedInstance]

- (void)publishPushNotification:(NSString *)message {
    PublishOptions *publishOptions = [PublishOptions new];
    [publishOptions assignHeaders:@{@"ios-alert":@"Alert message",
                                    @"ios-badge":@1,
                                    @"ios-sound":@"default"}];
    
    [backendless.messaging publish:@"default"
                           message:message
                    publishOptions:publishOptions
                          response:^(MessageStatus *status) {
                              NSLog(@"Status: %@", status);
                          }
                             error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault);
                             }];
}

let backendless = Backendless.sharedInstance()

func publishPushNotification(message: String) {
   let publishOptions = PublishOptions()
   publishOptions.assignHeaders(["ios-alert":"Alert message",
                                 "ios-badge":1,
                                 "ios-sound":"default"])
    
   backendless.messaging.publish(
       "default",
       message: message,
       publishOptions:publishOptions,
       response: {
           (status: MessageStatus?) -> Void in
           print("Status: \(status)")
   },
       error: {
           (fault: Fault?) -> Void in
           print("Server reported an error: \(fault)")
   })
}

Targetting a group of devices

#define backendless [Backendless sharedInstance]

- (void)publishPushNotification:(NSString *)message {
    PublishOptions *publishOptions = [PublishOptions new];
    [publishOptions assignHeaders:@{@"ios-alert":@"Alert message",
                                    @"ios-badge":@1,
                                    @"ios-sound":@"default"}];
    
    DeliveryOptions *deliveryOptions = [DeliveryOptions new];
    [deliveryOptions pushBroadcast:(FOR_IOS | FOR_ANDROID)];
    
    [backendless.messaging publish:@"default"
                           message:@"Push notification message"
                    publishOptions:publishOptions
                   deliveryOptions:deliveryOptions
                          response:^(MessageStatus *status) {
                              NSLog(@"Status: %@", status);
                          }
                             error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault);
                             }];
}
let backendless = Backendless.sharedInstance()

 func publishPushNotification(message: String) {
    let publishOptions = PublishOptions()
    publishOptions.assignHeaders(["ios-alert":"Alert message",
                                  "ios-badge":1,
                                  "ios-sound":"default"])
    
    let deliveryOptions = DeliveryOptions()
    deliveryOptions.pushBroadcast(FOR_IOS.rawValue | FOR_ANDROID.rawValue)
    
    backendless.messaging.publish(
        "default",
        message: message,
        publishOptions:publishOptions,
        deliveryOptions:deliveryOptions,
        response: {
            (status: MessageStatus?) -> Void in
            print("Status: \(status)")
    },
        error: {
            (fault: Fault?) -> Void in
            print("Server reported an error: \(fault)")
    })
 }

Targeting specific devices

#define backendless [Backendless sharedInstance]

- (void)publishPushNotification:(NSString *)message forDevice:(NSString *)deviceId {
    PublishOptions *publishOptions = [PublishOptions new];
    [publishOptions assignHeaders:@{@"ios-alert":@"Alert message",
                                    @"ios-badge":@1,
                                    @"ios-sound":@"default"}];
    
    DeliveryOptions *deliveryOptions = [DeliveryOptions new];
    deliveryOptions.pushSinglecast = @[deviceId];
    
    [backendless.messaging publish:@"default"
                           message:@"Push notification message"
                    publishOptions:publishOptions
                   deliveryOptions:deliveryOptions
                          response:^(MessageStatus *status) {
                              NSLog(@"Status: %@", status);
                          }
                             error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault);
                             }];
}

let backendless = Backendless.sharedInstance()

 func publishPushNotification(message: String, deviceId:String) {
    let publishOptions = PublishOptions()
    publishOptions.assignHeaders(["ios-alert":"Alert message",
                                  "ios-badge":1,
                                  "ios-sound":"default"])
    
    let deliveryOptions = DeliveryOptions()
    deliveryOptions.pushSinglecast = [deviceId]
    
    backendless.messaging.publish(
        "default",
        message: message,
        publishOptions:publishOptions,
        deliveryOptions:deliveryOptions,
        response: {
            (status: MessageStatus?) -> Void in
            print("Status: \(status)")
    },
        error: {
            (fault: Fault?) -> Void in
            print("Server reported an error: \(fault)")
    })
 }

Delayed publishing

#define backendless [Backendless sharedInstance]

- (void)publishPushNotification:(NSString *)message withDelay:(NSTimeInterval)seconds {
    PublishOptions *publishOptions = [PublishOptions new];
    [publishOptions assignHeaders:@{@"ios-alert":@"Alert message",
                                    @"ios-badge":@1,
                                    @"ios-sound":@"default"}];
    
    DeliveryOptions *deliveryOptions = [DeliveryOptions new];
    deliveryOptions.publishAt = [NSDate dateWithTimeIntervalSinceNow:seconds];
    [deliveryOptions pushPolicy:PUSH_ONLY];
    
    [backendless.messaging publish:@"default"
                           message:message
                    publishOptions:publishOptions
                   deliveryOptions:deliveryOptions
                          response:^(MessageStatus *status) {
                              NSLog(@"Status: %@", status);
                          }
                             error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault);
                             }];
}
let backendless = Backendless.sharedInstance()

func publishPushNotification(message: String, delaySeconds: TimeInterval) {
   let publishOptions = PublishOptions()
   publishOptions.assignHeaders(["ios-alert":"Alert message",
                                 "ios-badge":1,
                                 "ios-sound":"default"])
    
   let deliveryOptions = DeliveryOptions()
   deliveryOptions.publishAt = Date(timeIntervalSinceNow:delaySeconds)
   deliveryOptions.pushPolicy(PUSH_ONLY)
    
   backendless.messaging.publish(
       "default",
       message: message,
       publishOptions:publishOptions,
       deliveryOptions:deliveryOptions,
       response: {
           (status: MessageStatus?) -> Void in
           print("Status: \(status)")
   },
       error: {
           (fault: Fault?) -> Void in
           print("Server reported an error: \(fault)")
   })
}

 

Cancel Device Registration

To cancel the registration of a device with Backendless, an application can use the API described below:

#define backendless [Backendless sharedInstance]

// blocking methods:
// Remove device registration for the current device
- (id)[backendless.messaging unregisterDevice];

// Remove device registration for a device with deviceId
- (id)[backendless.messaging unregisterDevice:(NSString *)deviceId];

// non-blocking methods:
// Remove device registration for the current device
- (void)[backendless.messaging unregisterDevice:(void(^)(id))responseBlock 
                                          error:(void(^)(Fault *))errorBlock];

// Remove device registration for a device with deviceId
- (void)[backendless.messaging unregisterDevice:(NSString *)deviceId 
                                       response:(void(^)(id))responseBlock 
                                          error:(void(^)(Fault *))errorBlock];                      
let backendless = Backendless.sharedInstance()

// blocking methods:
// Remove device registration for the current device
func backendless.messaging.unregisterDevice() -> Any

// Remove device registration for a device with deviceId
func backendless.messaging.unregisterDevice(deviceId: String) -> Any

// non-blocking methods:
// Remove device registration for the current device
func backendless.messaging.unregisterDevice(responseBlock: ((Any) -> Void), 
                                            error: ((Fault?) -> Void))

// Remove device registration for a device with deviceId
func backendless.messaging.unregisterDevice(deviceId: String, 
                                            response: ((Any) -> Void), 
                                            error: ((Fault) -> Void))

where:

deviceId        - device Identifier;

responder     - for asynchronous API calls - Responder instance to receive the result or fault objects.

Return value:
  A boolean value indicating whether registration cancellation has succeeded. True - successful cancellation, false otherwise.

Errors:

The following errors may occur during the device registration cancellation API callThe following errors may occur during the device registration API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

Error Code

Description

5001

Unable to cancel device registration - unknown device ID.

Example:

#define backendless [Backendless sharedInstance]

- (void)cancelDeviceRegistration:(NSString *)deviceId {
    [backendless.messaging unregisterDevice:deviceId
                                   response:^(id result) {
                                       NSLog(@"Device registration canceled: %@", result);
                                   }
                                      error:^(Fault *fault) {
                                          NSLog(@"Server reported an error: %@", fault);
                                      }];
}
let backendless = Backendless.sharedInstance()

 func cancelDeviceRegistration(deviceId: String) {
    backendless.messaging.unregisterDevice(deviceId,
                                           response: {
                                            (result : Any?) -> Void in
                                            print("Device registration canceled: \(result)")
    },
                                           error: {
                                            (fault : Fault?) -> Void in
                                            print("Server reported an error: \(fault)")
    })
 }

Publish/Subscribe Messaging

Message Publishing

Application can publish messages to Backendless for subsequent distribution to subscribers. A message must be published to a channel (or a group of channels). Backendless supports unlimited number of channels per application. Channels can be used as a filtering mechanism - channel subscribers see messages published only to the channel. Message publishing supports the following scenarios:

 

Publishing with message headers - headers is a collection of name/value pairs. A subscriber can set filters expressed as SQL "where clause" queries which Backendless uses to determine if a message should be delivered to the subscriber. When the query matches the published data in message headers, message is delivered to the corresponding subscriber. See example.
 
Publishing to a subtopic - Subtopics provide an additional level of message filtering.

Multiple subtopics can be defined within a channel. Both publishers and subscribers can specify a subtopic within a channel. Subtopic names can be defined using a multi-tiered format:

maintoken[.secondaryToken][.additionalToken]

 

To receive messages from more than one subtopic, subscribers can use the wildcard character (*) in place of any tokens in the subtopic name. For instance, a subscriber could subscribe to the following subtopic: "news..business.*", and the publisher sends messages to "news.business.newyork" and "news.business.tokyo". In this case the messages published to either subtopic will be delivered to the consumer.

 

The wildcard character in the last position will match any token in that position as well as tokens after it. For instance, subtopic com.foo.* will match all of the following: com.foo.bar, com.foo.abc.def, etc. However, the wildcard character in any position other than the last will match only one token. For example, subtopic com.*.foo will match com.abc.foo and com.123.foo, but will not match com.foo.

See example.
 

Delayed publishing - Backendless immediately processes any published messages and delivers them to subscribers without any delay. However, publishers can specify the time when the message should be processed. This is applicable to all the publishing options listed above. Message processing can be canceled at any time using the message cancellation API. See example.
 
Scheduled (repeated) publishing - Backendless supports repeated message processing - a message is published once, but delivered to subscribers with the specified frequency. Repeated delivery can stop either at the specified time or they can be canceled using the message cancellation API. For instance, this could be used for reminders or scheduled tasks. See example.

 

Method Signatures

Publishes a message to the specified channel.

// These methods publish a message to the specified channel.
// The only difference between the methods is sync vs. async and how
// they deliver return value or faults.

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
-(MessageStatus *)publish:(NSString *)channelName message:(id)message;

// If the server returns an error, it is delivered through the "fault" object           
-(MessageStatus *)publish:(NSString *)channelName 
                           message:(id)message 
                           error:(Fault **)fault;
                           
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
// Server's return value and errors are delivered through the responder object
-(void)publish:(NSString *)channelName message:(id)message 
                responder:(id <IResponder>)responder;
                
// Server's return value and errors are delivered through the block-based callbacks
-(void)publish:(NSString *)channelName 
                message:(id)message 
                response:(void(^)(MessageStatus *))responseBlock 
                error:(void(^)(Fault *))errorBlock;
// These methods publish a message to the specified channel.
// The only difference between the methods is sync vs. async and how
// they deliver return value or faults.

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
func publish(_ channelName: String!, 
             message message: AniObject!) -> MessageStatus

// If the server returns an error, it is delivered through the "fault" object             
func publish(_ channelName: String!, 
             message message: AniObject!, 
             error fault: AutoreleasingUnsafeMutablePointer<Fault?>) -> MessageStatus
             
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
// Server's return value and errors are delivered through the responder object
func publish(_ channelName: String!, 
             message message: AniObject!, 
             responder responder: IResponder!) -> Void
                
// Server's return value and errors are delivered through the block-based callbacks
func publish(_ channelName: String!, 
             message message: AniObject!, 
             response responseBlock: ((DeviceRegistration!) -> Void)!, 
             error errorBlock: ((Fault!) -> Void)!) -> Void

Publishes a message to the specified channel with publish options. Publish options allow to set message headers, publisher ID and subtopic.

// These methods publish a message to the specified channel with publish options.
// Use these methods to send additional message headers (including specialized
// headers for push notifications), publisher ID and channel's subtopic.
// The only difference between the methods is sync vs. async and how
// they deliver return value or faults.

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
-(MessageStatus *)publish:(NSString *)channelName 
                           message:(id)message 
                           publishOptions:(PublishOptions *)publishOptions;
                           
// If the server returns an error, it is delivered through the "fault" object           
-(MessageStatus *)publish:(NSString *)channelName 
                           message:(id)message 
                           publishOptions:(PublishOptions *)publishOptions 
                           error:(Fault **)fault;
                           
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
// Server's return value and errors are delivered through the responder object
-(void)publish:(NSString *)channelName 
                message:(id)message 
                publishOptions:(PublishOptions *)publishOptions 
                responder:(id <IResponder>)responder;

// Server's return value and errors are delivered through the block-based callbacks
-(void)publish:(NSString *)channelName 
                message:(id)message 
                publishOptions:(PublishOptions *)publishOptions 
                response:(void(^)(MessageStatus *))responseBlock 
                error:(void(^)(Fault *))errorBlock;
// These methods publish a message to the specified channel with publish options.
// Use these methods to send additional message headers (including specialized
// headers for push notifications), publisher ID and channel's subtopic.
// The only difference between the methods is sync vs. async and how
// they deliver return value or faults.

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
func publish(_ channelName: String!, 
             message message: AniObject!, 
             publishOptions publishOptions: PublishOptions) -> MessageStatus
                           
// If the server returns an error, it is delivered through the "fault" object           
func publish(_ channelName: String!, 
             message message: AniObject!, 
             publishOptions publishOptions: PublishOptions, 
             error fault: AutoreleasingUnsafeMutablePointer<Fault?>) -> MessageStatus
             
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
// Server's return value and errors are delivered through the responder object
func publish(_ channelName: String!, 
             message message: AniObject!, 
             publishOptions publishOptions: PublishOptions, 
             responder responder: IResponder!) -> Void

// Server's return value and errors are delivered through the block-based callbacks
func publish(_ channelName: String!, 
             message message: AniObject!, 
             publishOptions publishOptions: PublishOptions, 
             response responseBlock: ((DeviceRegistration!) -> Void)!, 
             error errorBlock: ((Fault!) -> Void)!) -> Void

Publishes a message to the specified channel with publish and delivery options. Publish options allow to set message headers, publisher ID and subtopic. Delivery options can be used to:

1. Designate the message as "push notification only" or both a push notification and a pub/sub message.
2. Publish the message at a later time (delayed publishing).
3. Specify that the message should be republished multiple times (repeated publishing).

// These methods publish a message to the specified channel with publish options 
// and delivery options. Use these methods to send additional message headers 
// (including specialized headers for push notifications), publisher ID 
// and channel's subtopic. Additionally, configure delivery options to chose
// between singlecast (targetted delivery to specific devices) and broadcast
// (delivered to all devices of designates OS's), delayed publishing or
// repeated publishing.
// The only difference between the methods is sync vs. async and how
// they deliver return value or faults.

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
-(MessageStatus *)publish:(NSString *)channelName 
                           message:(id)message 
                           publishOptions:(PublishOptions *)publishOptions 
                           deliveryOptions:(DeliveryOptions *)deliveryOptions;
                           
// If the server returns an error, it is delivered through the "fault" object           
-(MessageStatus *)publish:(NSString *)channelName 
                           message:(id)message 
                           publishOptions:(PublishOptions *)publishOptions 
                           deliveryOptions:(DeliveryOptions *)deliveryOptions 
                           error:(Fault **)fault;
                           
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
// Server's return value and errors are delivered through the responder object
-(void)publish:(NSString *)channelName 
                message:(id)message 
                publishOptions:(PublishOptions *)publishOptions 
                deliveryOptions:(DeliveryOptions *)deliveryOptions 
                responder:(id <IResponder>)responder;

// Server's return value and errors are delivered through the block-based callbacks
-(void)publish:(NSString *)channelName 
                message:(id)message 
                publishOptions:(PublishOptions *)publishOptions 
                deliveryOptions:(DeliveryOptions *)deliveryOptions 
                response:(void(^)(MessageStatus *))responseBlock 
                error:(void(^)(Fault *))errorBlock;
// These methods publish a message to the specified channel with publish options 
// and delivery options. Use these methods to send additional message headers 
// (including specialized headers for push notifications), publisher ID 
// and channel's subtopic. Additionally, configure delivery options to chose
// between singlecast (targetted delivery to specific devices) and broadcast
// (delivered to all devices of designates OS's), delayed publishing or
// repeated publishing.
// The only difference between the methods is sync vs. async and how
// they deliver return value or faults.

// SYNCHRONOUS (BLOCKING) APIS
// ==================================================
// If the server returns an error, it is thrown as an exception
func publish(_ channelName: String!, 
             message message: AniObject!, 
             publishOptions publishOptions: PublishOptions, 
             deliveryOptions deliveryOptions: DeliveryOptions) -> MessageStatus
                           
// If the server returns an error, it is delivered through the "fault" object           
func publish(_ channelName: String!, 
             message message: AniObject!, 
             publishOptions publishOptions: PublishOptions, 
             deliveryOptions deliveryOptions: DeliveryOptions, 
             error fault: AutoreleasingUnsafeMutablePointer<Fault?>) -> MessageStatus
             
// ASYNCHRONOUS (NON-BLOCKING) APIS
// ==================================================
// Server's return value and errors are delivered through the responder object
func publish(_ channelName: String!, 
             message message: AniObject!, 
             deliveryOptions deliveryOptions: DeliveryOptions, 
             response responseBlock: ((DeviceRegistration!) -> Void)!, 
             error errorBlock: ((Fault!) -> Void)!) -> Void

// Server's return value and errors are delivered through the block-based callbacks
func publish(_ channelName: String!, 
             message message: AniObject!, 
             publishOptions publishOptions: PublishOptions, 
             deliveryOptions deliveryOptions: DeliveryOptions, 
             response responseBlock: ((DeviceRegistration!) -> Void)!, 
             error errorBlock: ((Fault!) -> Void)!) -> Void

where:

channelName - name of the channel to publish the message to. If the channel does not exist, Backendless automatically creates it.
message - object to publish. The object can be of any data type - a primitive value, String, Date, a  user-defined complex type, a collection or an array of these types.
publishOptions - an instance of PublishOptions. When provided may contain publisher ID (an arbitrary, application-specific string value identifying the publisher), subtopic value and a collection of headers.
deliveryOptions - an instance of DeliveryOptions. When provided may specify options for message delivery such as: deliver only as a push notification (as opposed to push notification and a pub/sub message), delayed delivery or repeated delivery.

Return value:

MessageStatus        - a data structure which contains message ID and message status:

@interface MessageStatus : NSObject
    @property (strong, nonatomic) NSString *messageId;
    @property (strong, nonatomic) NSNumber *status;
    @property (strong, nonatomic) NSString *errorMessage;
    -(id)initWithId:(NSString *)messageId;
    -(id)initWithId:(NSString *)messageId status:(PublishStatusEnum)status;
    -(id)initWithId:(NSString *)messageId status:(PublishStatusEnum)status errorMessage:(NSString *)errorMessage;
    
    -(PublishStatusEnum)valStatus;
    -(void)status:(PublishStatusEnum)status;
@end
class MessageStatus : NSObject {
        
   var messageId: String?
   var status: Int = 0
   var errorMessage: String?
}

Supported message statuses are:

PUBLISHED - message has been successfully published
SCHEDULED - message has been put into queue for delivery, it will be published immediately as soon as computing resources become available within Backendless
FAILED - message publishing failed. See the errorMessage property in MessageStatus for details.

Errors:

The following errors may occur during the message publishing API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

 

Error Code

Description

5003

Invalid repeatExpiresAt date in delivery options.

5007

User does not have the permission to publish messages

5030

Invalid publishAt date in the delivery options.

Examples:

Basic message publishing

Publishing with message headers

Publishing to a subtopic

Delayed publishing

Repeated publishing

 

Basic message publishing

Synchronous Publish

  -(MessageStatus *)publishMessageSync:(NSString *)message
    {
        @try {
            MessageStatus *messageStatus = [backendless.messaging publish:"default" message:message];
            NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
            return messageStatus;
        }
    
        @catch (Fault *fault) {
            NSLog(@"FAULT = %@", fault;
            return nil;
        }
    }
func publishMessageSync(message: String) -> MessageStatus? {
        
   var error: Fault?
   let messageStatus = backendless.messaging.publish("default", message: message, error: &error)
   if error == nil {
      print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
      return messageStatus
   }
   else {
     print("Server reported an error: \(error)")
     return nil
   }
}

Asynchronous Publish

-(void)publishMessageAsync:(NSString *)deviceId {
    
   [backendless.messaging
       publish:"default" message:message
           response:^(MessageStatus *messageStatus) {
              NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
           }
           error:^(Fault *fault) {
              NSLog(@"FAULT = %@", fault);
            }
   ];
}
func publishMessageAsync(message: String) {
        
   backendless.messaging.publish("default", message: message,
     response:{ ( messageStatus : MessageStatus!) -> () in
        print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
     },
     error: { ( fault : Fault!) -> () in
        print("Server reported an error: \(fault)")
     }
   )
}

Publishing with message headers

Synchronous Publish

    -(MessageStatus *)publishMessageFromSync:(NSString *)message from:(NSString *)name
    {
        PublishOptions *publishOptions = [PublishOptions new];
        [publishOptions addHeader:@"publisher_name" value:name];
    
        @try {
            MessageStatus *messageStatus = [backendless.messaging publish:"default" 
                                            message:message publishOptions:publishOptions];
            NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
            return messageStatus;
        }
    
        @catch (Fault *fault) {
            NSLog(@"FAULT = %@", fault;
            return nil;
        }
    }
    func publishMessageFromSync(message: String, name: String) -> MessageStatus? {
        
        let publishOptions = PublishOptions()
        publishOptions.addHeader("publisher_name", value: name)
        
        var error: Fault?
        let messageStatus = backendless.messaging.publish("default", 
                                 message: message, 
                                 publishOptions:publishOptions, 
                                 error: &error)
        if error == nil {
            print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            return messageStatus
        }
        else {
            print("Server reported an error: \(error)")
            return nil
        }
    }

Asynchronous Publish

    -(MessageStatus *)publishMessageFromAsync:(NSString *)message from:(NSString *)name
    {
        PublishOptions *publishOptions = [PublishOptions new];
        [publishOptions addHeader:@"publisher_name" value:name];
    
        [backendless.messagingService
            publish:"default" message:message publishOptions:publishOptions
            response:^(MessageStatus *messageStatus) {
                NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
            }
            error:^(Fault *fault) {
                NSLog(@"FAULT = %@", fault);
            }
        ];
    }
    func publishMessageFromAsync(message: String, name: String) {
        
        let publishOptions = PublishOptions()
        publishOptions.addHeader("publisher_name", value: name)
        
        backendless.messaging.publish("default", message: message, publishOptions:publishOptions,
            response:{ ( messageStatus : MessageStatus!) -> () in
                print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            },
            error: { ( fault : Fault!) -> () in
                print("Server reported an error: \(fault)")
            }
        )
    }

Publishing to a subtopic
Synchronous Publish

 -(MessageStatus *)publishMessageForSubtopicSync:(NSString *)message subtopic:(NSString *)subtopic
    {
        PublishOptions *publishOptions = [PublishOptions new];
        publishOptions.subtopic = subtopic;
    
        @try {
            MessageStatus *messageStatus = [backendless.messaging publish:"default" 
                                            message:message publishOptions:publishOptions];
            NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
            return messageStatus;
        }
    
        @catch (Fault *fault) {
            NSLog(@"FAULT = %@", fault;
            return nil;
        }
    }
    func publishMessageForSubtopicSync(message: String, subtopic: String) -> MessageStatus? {
        
        let publishOptions = PublishOptions()
        publishOptions.subtopic = subtopic
        
        var error: Fault?
        let messageStatus = backendless.messaging.publish("default", 
                            message: message, 
                            publishOptions:publishOptions, 
                            error: &error)
        if error == nil {
            print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            return messageStatus
        }
        else {
            print("Server reported an error: \(error)")
            return nil
        }
    }

Asynchronous Publish

    -(MessageStatus *)publishMessageSubtopicAsync:(NSString *)message subtopic:(NSString *)subtopic
    {
        PublishOptions *publishOptions = [PublishOptions new];
        publishOptions.subtopic = subtopic;
    
        [backendless.messagingService
            publish:"default" message:message publishOptions:publishOptions
            response:^(MessageStatus *messageStatus) {
                NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
            }
            error:^(Fault *fault) {
                NSLog(@"FAULT = %@", fault);
            }
        ];
    }
    func publishMessageForSubtopicAsync(message: String, subtopic: String) {
        
        let publishOptions = PublishOptions()
        publishOptions.subtopic = subtopic
        
        backendless.messaging.publish("default", message: message, publishOptions:publishOptions,
            response:{ ( messageStatus : MessageStatus!) -> () in
                print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            },
            error: { ( fault : Fault!) -> () in
                print("Server reported an error: \(fault)")
            }
        )
    }

Delayed publishing

Synchronous Publish

    -(MessageStatus *)publishMessageSync:(NSString *)message withDelay:(NSTimeInterval)seconds
    {
        DeliveryOptions *deliveryOptions = [DeliveryOptions new];
        deliveryOptions.publishAt = [NSDate dateWithTimeIntervalSinceNow:seconds];
        [deliveryOptions pushPolicy:PUSHONLY];
    
        @try {
            MessageStatus *result = [backendless.messagingService publish:"default" 
                                     message:message 
                                     deliveryOptions:deliveryOptions];
            NSLog(@"MessageStatus = %@ <%@>", result.messageId, result.status);
        return result;
        }
    
        @catch (Fault *fault) {
            NSLog(@"FAULT = %@", fault);
            return nil;
        }
    }
    func publishMessageSync(message: String, seconds: Double) -> MessageStatus? {
        
        let deliveryOptions = DeliveryOptions()
        deliveryOptions.publishAt = NSDate(timeIntervalSinceNow: seconds)
        deliveryOptions.pushPolicy(PUSH_ONLY)
        
        var error: Fault?
        let messageStatus = backendless.messaging.publish("default", 
                                message: message, 
                                deliveryOptions:deliveryOptions, 
                                error: &error)
        if error == nil {
            print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            return messageStatus
        }
        else {
            print("Server reported an error: \(error)")
            return nil
        }
    }

Asynchronous Publish

    -(void)publishMessageAsync:(NSString *)message withDelay:(NSTimeInterval)seconds
    {
        DeliveryOptions *deliveryOptions = [DeliveryOptions new];
        deliveryOptions.publishAt = [NSDate dateWithTimeIntervalSinceNow:seconds];
        [deliveryOptions pushPolicy:PUSH_ONLY];
    
        [backendless.messagingService
            publish:"default" message:message deliveryOptions:deliveryOptions
            response:^(MessageStatus *messageStatus) {
                NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
            }
            error:^(Fault *fault) {
                NSLog(@"FAULT = %@", fault);
            }
        ];
    }
    func publishMessageAsync(message: String, seconds: Double) {
        
        let deliveryOptions = DeliveryOptions()
        deliveryOptions.publishAt = NSDate(timeIntervalSinceNow: seconds)
        deliveryOptions.pushPolicy(PUSH_ONLY)
        
        backendless.messaging.publish("default", message: message, deliveryOptions:deliveryOptions,
            response:{ ( messageStatus : MessageStatus!) -> () in
                print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            },
            error: { ( fault : Fault!) -> () in
                print("Server reported an error: \(fault)")
            }
        )
    }

Repeated publishing
Synchronous Publish

   -(MessageStatus *)publishMessageSync:(NSString *)message 
                           startAt:(NSDate *)start 
                           expireAt:(NSDate *)expire 
                           repeatEvery:(int)seconds
    {
        DeliveryOptions *deliveryOptions = [DeliveryOptions new];
        deliveryOptions.publishAt = start;
        deliveryOptions.repeatExpiresAt = expire;
        [deliveryOptions repeatEvery:seconds];
    
        @try {
            MessageStatus *result = [backendless.messagingService 
                                          publish:"default" 
                                          message:message 
                                          deliveryOptions:deliveryOptions];
            NSLog(@"MessageStatus = %@ <%@>", result.messageId, result.status);
            return result;
        }
    
        @catch (Fault *fault) {
            NSLog(@"FAULT = %@", fault);
            return nil;
        }
    }
    func publishMessageSync(message: String, 
                      start: NSDate, 
                      expire: NSDate, 
                      seconds: Int) -> MessageStatus? {
        
        let deliveryOptions = DeliveryOptions()
        deliveryOptions.publishAt = start
        deliveryOptions.repeatExpiresAt = expire;
        deliveryOptions.repeatEvery(seconds)
        
        var error: Fault?
        let messageStatus = backendless.messaging.publish("default", 
                                  message: message, 
                                  deliveryOptions:deliveryOptions, 
                                  error: &error)
        if error == nil {
            print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            return messageStatus
        }
        else {
            print("Server reported an error: \(error)")
            return nil
        }
    }

Asynchronous Publish

  -(void)publishMessageAsync:(NSString *)message 
                   startAt:(NSDate *)start 
                   expireAt:(NSDate *)expire 
                   repeatEvery:(int)seconds
    {
        DeliveryOptions *deliveryOptions = [DeliveryOptions new];
        deliveryOptions.publishAt = start;
        deliveryOptions.repeatExpiresAt = expire;
        [deliveryOptions repeatEvery:seconds];
    
        [backendless.messagingService
            publish:"default" message:message deliveryOptions:deliveryOptions
            response:^(MessageStatus *messageStatus) {
                NSLog(@"MessageStatus = %@ <%@>", messageStatus.messageId, messageStatus.status);
            }
            error:^(Fault *fault) {
                NSLog(@"FAULT = %@", fault);
            }
        ];
    }
    func publishMessageAsync(message: String, start: NSDate, expire: NSDate, seconds: Int) {
        
        let deliveryOptions = DeliveryOptions()
        deliveryOptions.publishAt = start
        deliveryOptions.repeatExpiresAt = expire;
        deliveryOptions.repeatEvery(seconds)
        
        backendless.messaging.publish("default", message: message, deliveryOptions:deliveryOptions,
            response:{ ( messageStatus : MessageStatus!) -> () in
                print("MessageStatus = \(messageStatus.status) ['\(messageStatus.messageId)']")
            },
            error: { ( fault : Fault!) -> () in
                print("Server reported an error: \(fault)")
            }
        )
    }

 

Message Filtering

Backendless message filtering is a powerful mechanism enabling conditional message delivery, interest-based subscriptions and private messaging. A subscription request may include filters in the form of subtopics and selectors. Backendless applies subscriber's filters to every message published into the channel and they match, the message is delivered to the subscriber.

 

Subtopics

Multiple subtopics can be defined within a channel. Both publishers and subscribers can specify a subtopic within a channel. Subtopic names can be defined using a multi-tiered format:

maintoken[.secondaryToken][.additionalToken]

 

To receive messages from more than one subtopic, subscribers can use the wildcard character (*) in place of any tokens in the subtopic name. For instance, a subscriber could subscribe to the following subtopic: "news..business.*", and the publisher sends messages to "news.business.newyork" and "news.business.tokyo". In this case the messages published to either subtopic will be delivered to the consumer.

 

The wildcard character in the last position will match any token in that position as well as tokens after it. For instance, subtopic com.foo.* will match all of the following: com.foo.bar, com.foo.abc.def, etc. However, the wildcard character in any position other than the last will match only one token. For example, subtopic com.*.foo will match com.abc.foo and com.123.foo, but will not match com.foo.

 

Selectors

A selector is a query expressed using the SQL-92 syntax and formatted as the condition part of the SQL's WHERE clause. A query condition must reference the headers of the published messages. When a message is published and a subscriber has a selector query, Backendless executes the query on the headers of the published message. If the result of the query is true, the message is delivered to the subscriber. Consider the following example where the subscriber will receive only messages containing the "city" header with the value of "Tokyo":

 

Publisher:

 

#define backendless [Backendless sharedInstance]

-(void)publishWeather:(NSString *)city 
                       humidity:(NSNumber *)humidity 
                       temperature:(NSNumber *)temperature {
    PublishOptions *publishOptions = [PublishOptions new];
    [publishOptions addHeader:@"city_header" value:city];
    
    Weather *weather = [Weather new];
    weather.humidity = humidity;
    weather.temperature = temperature;
    
    [backendless.messaging publish:@"weather_channel"
                           message:weather
                    publishOptions:publishOptions
                          response:^(MessageStatus *status) {
                              NSLog(@"Status: %@", status);
                          }
                             error:^(Fault *fault) {
                                 NSLog(@"Server reported an error: %@", fault.description);
                             }];
}
let backendless = Backendless.sharedInstance()!

func publishWeather(city: String, humidity: NSNumber, temperature: NSNumber) {
    let publishOptions = PublishOptions()
    publishOptions.addHeader("city_header", value: city)
    
    let weather = Weather()
    weather.humidity = humidity
    weather.temperature = temperature
    
    backendless.messaging.publish("weather_channel",
                     message: weather,
                     publishOptions: publishOptions,
                     response: {
                       (status: MessageStatus?) -> Void in
                       print("Status: \(status)")
                     },
                     error: {
                       (fault: Fault?) -> Void in
                       print("Server reported an error: \(fault?.description)")
                     })   
}

Subscriber:

 

#define backendless [Backendless sharedInstance]

-(void)subscribe {
    SubscriptionOptions *subscriptionOptions = [SubscriptionOptions new];
    subscriptionOptions.selector = @"city_header='Tokyo'";
    [backendless.messaging subscribe:@"weather_channel"
         subscriptionResponse:^(NSArray<Message *> *messages) {
           for (Message *message in messages) {
             NSString *city = [message.headers objectForKey:@"city_header"];
             NSLog(@"%@ : '%@'", city, message.data);
           }
         }
         subscriptionError:^(Fault *fault) {
           NSLog(@"Fault: %@", fault.description);
         }
         subscriptionOptions:subscriptionOptions
           response:^(BESubscription *subscription) {
             NSLog(@"Subscription: %@", subscription);
           }
           error:^(Fault *fault) {
             NSLog(@"Server reported an error: %@", fault.description);
           }];
}
let backendless = Backendless.sharedInstance()!

func subscribe() {
    let subscriptionOptions = SubscriptionOptions()
    subscriptionOptions.selector = "city_header = 'Tokyo'"
    backendless.messaging.subscribe("weather_channel",
            subscriptionResponse: {
              (messages: [Message]?) -> Void in
              for message:Message in messages! {
                let city: String = message.headers["city_header"]!
                print("\(city) : \(message.data)")
              }
           },
           subscriptionError: {
             (fault: Fault?) -> Void in
             print("Fault: \(fault?.description)")
           },
           subscriptionOptions: subscriptionOptions,
                                response: {
                                  (subscription: BESubscription?) -> Void in
                                  print("Subscription: \(subscription)")
                                },
                                error: {
                                  (fault: Fault?) -> Void in
                                  print("Server reported an error: \(fault?.description)")
                               })
}

Message Subscription

In order to receive published messages, application must subscribe to a channel using the API described below. Using the API, an application becomes an "API subscriber".

    Synchronous Methods

#define backendless [Backendless sharedInstance]

// creates a subscriber for the channel using default subscription options
-(BESubscription *)[backendless.messaging subscribe:(NSString *)channelName];

// creates a subscriber for the channel using default subscription options. 
// Messages published to the channel are delivered to the client via the
// "subscriptionResponseBlock" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionErrorBlock"
-(BESubscription *)[backendless.messaging subscribe:(NSString *)channelName 
                           subscriptionResponse:(void(^)(NSArray<Message*> *))subscriptionResponseBlock 
                           subscriptionError:(void(^)(Fault *))subscriptionErrorBlock];

// creates a subscriber for the channel using custom subscription options. 
// Messages published to the channel are delivered to the client via the 
// "subscriptionResponseBlock" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionErrorBlock"  
-(BESubscription *)[backendless.messaging subscribe:(NSString *)channelName 
                           subscriptionResponse:(void(^)(NSArray<Message*> *))subscriptionResponseBlock 
                           subscriptionError:(void(^)(Fault *))subscriptionErrorBlock 
                           subscriptionOptions:(SubscriptionOptions *)subscriptionOptions];
let backendless = Backendless.sharedInstance()

// creates a subscriber for the channel using default subscription options
func backendless.messaging.subscribe:(channelName: String) -> BESubscription

// creates a subscriber for the channel using default subscription options. 
// Messages published to the channel are delivered to the client via the 
// "subscriptionResponseBlock" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionError" block
func backendless.messaging.subscribe(channelName: String, 
                                     subscriptionResponse: ([Message] -> Void), 
                                     subscriptionError: (Fault -> Void))

// creates a subscriber for the channel using custom subscription options. 
// Messages published to the channel are delivered to the client via the 
// "subscriptionResponseBlock" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionError" block  
func backendless.messaging.subscribe(channelName: String, 
                                     subscriptionResponse: ([Message] -> Void), 
                                     subscriptionError: (Fault -> Void), 
                                     subscriptionOptions: SubscriptionOptions)

Return value:

BESubscription - An object representing the subscription. Can be used to customize message polling interval. Provides access to a unique identifier assigned to the subscription which can be used for subscription cancellation.

Asynchronous Methods:

#define backendless [Backendless sharedInstance]

// creates a subscriber for the channel using default subscription options
-(void)[backendless.messaging subscribe:(NSString *)channelName 
                                 response:(void(^)(BESubscription *))responseBlock 
                                 error:(void(^)(Fault *))errorBlock];

// creates a subscriber for the channel using default subscription options. 
// Messages published to the channel are delivered to the client via the 
// "subscriptionResponseBlock" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionErrorBlock"
-(void)[backendless.messaging subscribe:(NSString *)channelName 
                               subscriptionResponse:(void(^)(NSArray<Message*> *))subscriptionResponseBlock 
                               subscriptionError:(void(^)(Fault *))subscriptionErrorBlock 
                               response:(void(^)(BESubscription *))responseBlock 
                               error:(void(^)(Fault *))errorBlock];

// creates a subscriber for the channel using custom subscription options. 
// Messages published to the channel are delivered to the client via the 
// "subscriptionResponseBlock" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionErrorBlock"  
-(void)[backendless.messaging subscribe:(NSString *)channelName 
                               subscriptionResponse:(void(^)(NSArray<Message*> *))subscriptionResponseBlock 
                               subscriptionError:(void(^)(Fault *))subscriptionErrorBlock 
                               subscriptionOptions:(SubscriptionOptions *)subscriptionOptions 
                               response:(void(^)(BESubscription *))responseBlock 
                               error:(void(^)(Fault *))errorBlock];
let backendless = Backendless.sharedInstance()

// creates a subscriber for the channel using default subscription options
func backendless.messaging.subscribe(channelName: String, 
                                     response: (BESubscription -> Void), 
                                     error: (Fault -> Void)) -> Void

// creates a subscriber for the channel using default subscription options. 
// Messages published to the channel are delivered to the client via the 
// "subscriptionResponse" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionError" callback  
func backendless.messaging.subscribe(channelName: String, 
                                     subscriptionResponse: ([Message] -> Void), 
                                     subscriptionError: (Fault -> Void), 
                                     response: (BESubscription -> Void), 
                                     error: (Fault -> Void)) -> Void

// creates a subscriber for the channel using custom subscription options. 
// Messages published to the channel are delivered to the client via the 
// "subscriptionResponse" callback. Errors which may occur during message 
// retrieval are delivered through the "subscriptionError" callback  
func backendless.messaging.subscribe(channelName: String,