iOS Documentation

Last updated: Nov 6th, 2016

Getting Started

A Framework that gives you access to the powerful Mesosfer cloud platform from your iOS app. For more information about Mesosfer and the features, see Mesosfer Website and Mesosfer Documentations.

Download

  1. Download the latest Mesosfer framework here.
  2. Then drag Mesosfer.framework into the Frameworks sections in your Xsyntax project.
  3. Ensure the following frameworks and a library exist in your project :
    • SystemConfiguration.framework
    • Security.framework
    • QuartzCore.framework
    • CoreLocation.framework
    • CoreGraphics.framework
    • CFNetwork.framework
    • AudioToolbox.framework
    • libsqlite3.0.tbd

Setup

  1. Register to Mesosfer Cloud
  2. Create an application to get applicationId and clientKey
  3. Add this line below to your Application class to initialize Mesosfer SDK
[Mesosfer initializeWithApplicationId: @"YOUR-APPLICATION-ID" 
    clientKey: @"YOUR-CLIENT-KEY"]
Mesosfer.initialize(withApplicationId: "YOUR-APPLICATION-ID", 
    clientKey: "YOUR-CLIENT-KEY")
    

(Optional) You can add some custom setup :

  • Enable Mesosfer SDK debug logging by calling Mesosfer.+setLogLevel: before initialize SDK.
  • Mesosfer MFLogLevel mode : none, error, warning, info, debug

Managing Users

Mesosfer provides a specialized bucket User that automatically handles much of the functionality required for user management. With this bucket, you’ll be able to manage user account functionality in your app.

Register

The first thing your app will do is probably ask the user to register. The following code illustrates a typical register:


// create new instance of Mesosfer User
MFUser *user = [MFUser user];

// set default field
user.email = @"user.one@mesosfer.com";
user.password = @"user1234";
user.firstname = @"User";
user.lastname = @"One";

// set custom field
user["dateOfBirth"] = [NSDate date];
user["height"] = @177.5;
user["weight"] = @78;
user["isMarried"] = @YES;
user["myObject"] = [NSDictionary dictionary];
user["myArray"] = [NSArray array];

// execute register user asynchronous
[user registerAsyncWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
    if (succeeded) {
        // register succeeded
    } else {
        // register failed, show error message
    }
}

// create new instance of Mesosfer User
let user = MFUser()

// set default field
user.email = "user.one@mesosfer.com"
user.password = "user1234"
user.firstname = "User"
user.lastname = "One"

// set custom field
user["dateOfBirth"] = NSDate()
user["height"] = 177.5
user["weight"] = 78
user["isMarried"] = true
user["myObject"] = [String:AnyObject]()
user["myArray"] = [[String:AnyObject]]()

// execute register user asynchronous
user.registerAsync { (succeeded, error) in
    if succeeded {
        // register succeeded
    } else {
        // register failed, show error message
    }
}

This call will asynchronously create a new user in your Mesosfer App. Before it starts, the call will check to make sure that the email are unique. It also securely hashes the password in the cloud using bcrypt. We never store passwords in plaintext, nor will we ever transmit passwords back to the client in plaintext.

If a register isn’t successful, you should read the NSError that is returned. The most likely case is that the email has already been taken by another user. You should communicate this to your user clearly and ask them try a different email.

Login

After you allow users to register, you need be able to let them log in to their account in the future. To do this you can use several method for handling action in login process.

Session User

It would be bothersome if the user had to login every time they open your app. You can avoid this by using the cached currentUser object.

Whenever you use any register or login methods, the user is cached on disk. You can treat this cache as a session, and automatically assume the user is logged in:


MFUser *user = [MFUser currentUser];
if (user) {
    // user logged in, open main view
} else {
    // session not found, open login view
}

if let user = MFUser.currentUser() {
    // user logged in, open main view
} else {
    // session not found, open login view
}

Log In User

After you allow users to register, you need be able to let them login to their account in the future. To do this, you can use the class method logInAsync.


[MFUser logInAsyncWithEmail:@"username@mail.com" 
                   password:@"myPlainPassword"
                      block:^(MFUser * _Nullable user, NSError * _Nullable error) {
    if (error) {
        // error happen, show error message
        return;
    }

    // log in succeeded
}];

MFUser.logInAsync(withEmail: "myUsername", 
                   password: "myPlainPassword") { (user, error) in
    if let e = error as? NSError {
        // error happen, show error message
        return
    }

    // log in succeeded
}

Log Out User

You can clear the current user by logging them out:


[MFUser logOutAsyncWithBlock:^(NSError * _Nullable error) {
    if (error) {
        // error happen, show error message
        return;
    }

    // log out succeeded
}];
}

MFUser.logOutAsync { (error) in
    if let e = error as? NSError {
        // error happend, show error message
        return
    }

    // log out succeeded
}

Retreive Current User

Mesosfer allow you to retreive current user attributes and update all of them.

Fetch User

If you need to fetch data on a current user with the latest data that is in the cloud, you can call the fetchAsync method like so:


MFUser *user = [MFUser currentUser];
[user fetchAsyncWithBlock:^(MFUser * _Nullable user, NSError * _Nullable error) {
    if (error) {
        // error happen, show error message
        return;
    }

    // fetch user data succeeded
}];

let user = MFUser.currentUser()
user.fetchAsync { (user, error) in
    if let e = error as? NSError {
        // error happen, show error message
        return
    }

    // fetch user data succeeded
}

This will automatically update currentUser with the latest data from cloud.

Update User

After logged in, you can update your data that stored in cloud using method saveAsync.


MFUser *user = [MFUser currentUser];

// set default field
user.firstname = @"Updated firstname";
user.lastname = @"Updated lastname";

// set custom field
user[@"dateOfBirth"] = [NSDate date];
user[@"height"] = @177.5;
user[@"weight"] = @78;
user[@"isMarried"] = @YES;

// execute update user
[user saveAsyncWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
    if (succeeded) {
        // update succeeded
    } else {
        // update failed, show error message
    }
}];

let user = MFUser()
// set default field
user.firstname = "Updated firstname"
user.lastname = "Updated lastname"

// set custom field
user["dateOfBirth"] = NSDate()
user["height"] = 177.5
user["weight"] = 78
user["isMarried"] = true

// execute update user
user.saveAsync { (succeeded, error) in
    if succeeded {
        // update succeeded
    } else {
        // update failed, show error message
    }
}

Change Password

If you want to change your current password, use method changePasswordAsync:


MFUser *user = [MFUser currentUser];
[user changePasswordAsyncWithOldPassword:@"oldPassword"
                             newPassword:@"newPassword"
                                   block:^(BOOL succeeded, NSError * _Nullable error) {
    if (succeeded) {
        // password changed successfully
    } else {
        // error happen, show error message
    }
}];

let user = MFUser.currentUser()
user.changePasswordAsync(withOldPassword: oldPassword, 
                             newPassword: newPassword, block: { (succeeded, error) in
    if succeeded {
        // password changed successfully
    } else {
        // error happen, show error message
    }
})

Query User

Mesosfer allow you to retreive users data using special query. There are serveral types of Mesosfer Query : Basic Query, Query Constraint, and Query on String, and Counting.

Basic Query User

In many cases, there is a condition that need to specify which users you want to retrieve. The MFQuery offers different ways to retrieve a list of users. The general pattern is to create a MFQuery, put conditions on it, and then retrieve an Array of matching MFUser using the findAsync method with a MFArrayResultBlock. For example, to retrieve users with a firstname, use the whereKey:equalTo: method to constrain the value for a key:


MFQuery *query = [MFUser query];
[query whereKey:@"firstname" equalTo:@"John"];
[query findAsyncWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
    if (error) {
        // error happen, show error message
        return;
    }

    // users found, display result in table view
}];

let query = MFUser.query()
query.whereKey("firstname", equalTo: "John");
findAsync { (users, error) in
    if let e = error as? NSError {
        // error happen, show error message
        return
    }

    // users found, display result in table view
}

Query Constraint

There are several ways to put constraints on the objects found by a MFQuery. You can filter out users with a particular key-value pair with whereKey:notEqualTo::

[query whereKey:@"firstname" notEqualTo:@"John"];
query.whereKey("firstname", notEqualTo: "John")

You can give multiple constraints, and users will only be in the results if they match all of the constraints. In other words, it’s like an AND of constraints.

[query whereKey:@"firstname" notEqualTo:@"John"];
[query whereKey:@"height" greaterThan:@170];
query.whereKey("firstname", notEqualTo: "John")
query.whereKey("height", greaterThan: 170)

You can limit the number of results with setLimit. By default, results are limited to 100, but anything from 1 to 1000 is a valid limit:

[query setLimit:20]; // limit to at most 20 results
query.setLimit(20) // limit to at most 20 results

You can skip the first results with setSkip. This can be useful for pagination:

[query setSkip:10]; // skip the first 10 results
query.setSkip(10) // skip the first 10 results

For sortable types like numbers and strings, you can control the order in which results are returned:

// Sorts the results in ascending order by the user height
[query orderByAscending:@"height"];

// Sorts the results in descending order by the user height
[query orderByDescending:@"height"];
// Sorts the results in ascending order by the user height
query.order(byAscending: "height")

// Sorts the results in descending order by the user height
query.order(byDescending: "height")

You can add more sort keys to the query as follows:

// Sorts the results in ascending order by the user height field if the previous sort keys are equal.
[query addAscendingOrder:@"height"];

// Sorts the results in descending order by the user weight field if the previous sort keys are equal.
[query addDescendingOrder:@"weight"];
// Sorts the results in ascending order by the user height field if the previous sort keys are equal.
query.addAscendingOrder("height")

// Sorts the results in descending order by the user weight field if the previous sort keys are equal.
query.addDescendingOrder("weight")

For sortable types, you can also use comparisons in queries:

// Restricts to height < 170
[query whereKey:@"height" lessThan:@170];

// Restricts to height <= 170
[query whereKey:@"height" lessThanOrEqualTo:@170];

// Restricts to height > 170
[query whereKey:@"height" greaterThan:@170];

// Restricts to height >= 170
[query whereKey:@"height" greaterThanOrEqualTo:@170];
// Restricts to height < 170
query.whereKey("height", lessThan: 170)

// Restricts to height <= 170
query.whereKey("height", lessThanOrEqualTo: 170)

// Restricts to height > 170
query.whereKey("height", greaterThan: 170)

// Restricts to height >= 170
query.whereKey("height", greaterThanOrEqualTo: 170)

Query on String

Use whereKey:hasPrefix: to restrict to string values that start with a particular string. Similar to a MySQL LIKE operator, this is indexed so it is efficient for large datasets:

// Finds user email that start with 'john'.
MFQuery *query = [MFUser query];
[query whereKey:@"email" hasPrefix:@"john"];
// Finds user email that start with 'john'.
let query = MFUser.query()
query.whereKey("email", hasPrefix: "john")

The example above will match any MFUser where the value in the email String key starts with “john”. For example, both “john.doe@nomail.com” and “john.cena@nomail.com” will match, but “big.john@okmail.com” or “little.john@okmail.com” will not.

Use whereKey:hasSuffix: to restrict to string values that end with a particular string.

// Finds user email that end with '@okmail.com'.
MFQuery *query = [MFUser query];
[query whereKey:@"email" hasSuffix: @"@okmail.com"];
// Finds user email that end with '@okmail.com'.
let query = MFUser.query()
query.whereKey("email", hasSuffix: "@okmail.com")

The example above will match any MFUser where the value in the email String key ends with “@okmail.com”. For example, both “big.john@okmail.com” and “little.john@okmail.com” will match, but “john.doe@nomail.com” or “john.cena@nomail.com” will not.

Counting

If you just need to count how many objects match a query, but you do not need to retrieve all the objects that match, you can use count instead of find. For example, to count how many users have height greater than 170 centimetres:

MFQuery *query = [MFUser query];
[query whereKey:@"height" greaterThan:@170];
[query countAsyncWithBlock:^(int number, NSError * _Nullable error) {
    if (error) {
        // error happen, show error message
        return;
    }

    // counting users succeeded, show result
}];
let query = MFUser.query()
query.whereKey("height", greaterThan: 170)
query.countAsync { (count, error) in
    if let e = error as? NSError {
        // error happen, show error message
        return
    }

    // counting users succeeded, show result
}

Managing Data

Storing data on Mesosfer is built around the MFData. Each MFData contains key-value pairs of JSON-compatible data. This data is using schema, which means that you need to specify ahead of time what keys exist on each MFData from our Mesosfer Cloud. Then you can simply set a key-value pairs you want to save, and our backend will store it.

For example, let's say you set a Beacon parameters. A single MFData could contain :

"isActive":true, "major":1, "name":"Beacon One", "minor":284, "proximityUUID":"CB10023F-A318-3394-4199-A8730C7C1AEC"

Keys must be alphanumeric strings. Values can be String, Number, Date, Boolean, or even Array and Object - anything that can be JSON-encoded.

Saving Data

Let’s say you want to save the Beacon described above to the Mesosfer Cloud. The interface is similar to a Map, plus the saveAsync method:

MFData *data = [MFData dataWithBucket:@"Beacon"];

// set data
data[@"name"] = @"Beacon One";
data[@"proximityUUID"] = @"CB10023F-A318-3394-4199-A8730C7C1AEC";
data[@"major"] = @1;
data[@"minor"] = @284;
data[@"isActive"] = @YES;
data[@"timestamp"] = [NSDate date];

// execute save data
[data saveAsyncWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
    if (succeeded) {
        // data saved, show success message
    } else {
        // failed to save data, show error message
    }
}];
let data = MFData(withBucket: "Beacon")
// set data
data["name"] = "Beacon One"
data["proximityUUID"] = "CB10023F-A318-3394-4199-A8730C7C1AEC"
data["major"] = 1
data["minor"] = 284
data["isActive"] = true
data["timestamp"] = NSDate()

// execute save data
data.saveAsync { (succeeded, error) in
    if succeeded {
        // data saved, show success message
    } else {
        // failed to save data, show error message
    }
}

Retrieve a Data

If you need to fetch a data with the latest data that is in the cloud, you can call the fetchAsync method like so:

// create data from existing objecId
MFData *data = [MFData dataWithObjectId:@"objectId"];
// fetching the data
[data fetchAsyncWithBlock:^(MFData * _Nullable data, NSError * _Nullable error) {
    if (error) {
        // data failed to fetch, show error message
        return;
    }

    // data fetched
}];
// create data from existing objecId
let data = MFData(withObjectId: "objectId")
// fetching the data
data.fetchAsync { (data, error) in
    if let e = error as? NSError {
        // data failed to fetch, show error message
        return
    } 
    // data fetched
}

This will automatically update data with the latest data from cloud.

Querying Data

There are many other ways to retrieve data with Mesosfer Query. You can retrieve many data at once, put conditions on the data you wish to retrieve.

Basic Query

In many cases, there is a condition that need to specify which data you want to retrieve. The MFQuery offers different ways to retrieve a list of data. The general pattern is to create a MFQuery, put conditions on it, and then retrieve a Array of matching MFData using the findAsync method with a MFArrayResultBlock. For example, to retrieve Beacon data with a name, use the whereKey:equalTo method to constrain the value for a key:

MFQuery *query = [MFData queryWithBucket:@"Beacon"];
[query whereKey:@"name" equalTo:@"Beacon One"];
[query findAsyncWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
    if (error) {
        // error happen, show error message
        return;
    }

    // data beacon's found, display result in table view
}];
let query = MFData.query(withBucket: "Beacon")
query.whereKey("name", equalTo: "Beacon One")
query.findAsync(block: { (datas, error) in
    if let e = error as? NSError {
        // error happen, show error message
        return
    }

    // data beacon's found, show the result
})

Query Constraint

There are several ways to put constraints on the datas found by a MFQuery. You can filter out datas with a particular key-value pair with whereKey:notEqualTo::

[query whereKey:@"name" notEqualTo:@"Beacon One"];
query.whereKey("name", notEqualTo: "Beacon One")

You can give multiple constraints, and datas will only be in the results if they match all of the constraints. In other words, it’s like an AND of constraints.

[query whereKey:@"name" notEqualTo:@"Beacon One"];
[query whereKey:@"major" greaterThan:@1];
query.whereKey("name", notEqualTo: "Beacon One")
query.whereKey("major", greaterThan: 1)

You can limit the number of results with setLimit. By default, results are limited to 100, but anything from 1 to 1000 is a valid limit:

[query setLimit:20]; // limit to at most 20 result
query.setLimit(20) // limit to at most 20 results

You can skip the first results with setSkip. This can be useful for pagination:

[query setSkip:10]; // skip the first 10 results
query.setSkip(10) // skip the first 10 results

For sortable types like numbers and strings, you can control the order in which results are returned:

// Sorts the results in ascending order by the beacon's major
[query orderByAscending:@"major"];

// Sorts the results in descending order by the beacon's minor
[query orderByDescending:@"minor"];
// Sorts the results in ascending order by the beacon's major
query.order(byAscending: "major")

// Sorts the results in descending order by the beacon's minor
query.order(byDescending: "minor")

You can add more sort keys to the query as follows:

// Sorts the results in ascending order by the beacon's major field if the previous sort keys are equal.
[query addAscendingOrder:@"major"];

// Sorts the results in descending order by the beacon's minor field if the previous sort keys are equal.
[query addDescendingOrder:@"minor"];
// Sorts the results in ascending order by the beacon's major field if the previous sort keys are equal.
query.addAscendingOrder("major")

// Sorts the results in descending order by the beacon's minor field if the previous sort keys are equal.
query.addDescendingOrder("minor")

For sortable types, you can also use comparisons in queries:

// Restricts to major < 123
[query whereKey:@"major" lessThan:@123];

// Restricts to major <= 123
[query whereKey:@"major" lessThanOrEqualTo:@123];

// Restricts to major > 123
[query whereKey:@"major" greaterThan:@123];

// Restricts to major >= 123
[query whereKey:@"major" greaterThanOrEqualTo:@123];
// Restricts to major < 123
query.whereKey("major", lessThan: 123)

// Restricts to major <= 123
query.whereKey("major", lessThanOrEqualTo: 123)

// Restricts to major > 123
query.whereKey("major", greaterThan: 123)

// Restricts to major >= 123
query.whereKey("major", greaterThanOrEqualTo: 123)

Query on String

Use whereKey:hasPrefix: to restrict to string values that start with a particular string. Similar to a MySQL LIKE operator, this is indexed so it is efficient for large datasets:

// Finds beacon's name that start with 'Beacon'.
MFData *query = [MFData queryWithBucket:@"Beacon"];
[query whereKey:@"name" hasPrefix:@"Beacon"];
// Finds beacon's name that start with 'Beacon'.
let query = MFData.query(withBucket: "Beacon")
query.whereKey("name", hasPrefix: "Beacon")

The example above will match any MFData where the value in the name String key starts with “Beacon”. For example, both “Beacon One” and “Beacon Two” will match, but “First Beacon” or “Second Beacon” will not.

Use whereKey:hasSuffix: to restrict to string values that end with a particular string.

// Finds beacon's name that end with 'One'.
MFData *query = [MFData queryWithBucket:@"Beacon"];
[query whereKey:@"name" hasSuffix:@"One"];
// Finds beacon's name that end with 'One'.
let query = MFData.query(withBucket: "Beacon")
query.whereKey("name", hasSuffix: "One")

The example above will match any MFData where the value in the name String key ends with “One”. For example, “Beacon One” will match, but “One Beacon” will not.

Counting

If you only need to count how many datas match a query, but you do not need to retrieve all the datas that match, you can use count instead of find. For example, to count how many beacons have major greater than 123:

MFData *query = [MFData queryWithBucket:@"Beacon"];
[query whereKey:@"major" greaterThan:@123];
[query countAsyncWithBlock:^(int number, NSError * _Nullable error) {
    if (error) {
        // error happen, show error message
        return;
    }

    // counting data beacon's succeeded, show result number
}];
let query = MFData.query(withBucket: "Beacon")
query.whereKey("major", greaterThan: 123)
query.countAsync { (count, error) in
    if let e = error as? NSError {
        // error happen, show error message
        return
    }

    // counting data beacon's succeeded, show result
})

Update Data

After getting the data, you can update your data that stored in cloud using method saveAsync.

MFData *data; // fetched data

// set data
data[@"name"] = @"Beacon Two";
data[@"proximityUUID"] = @"CB10023F-A318-3394-4199-A8730C7C1AEC";
data[@"major"] = @2;
data[@"minor"] = @284;
data[@"isActive"] = @NO;
data[@"timestamp"] = [NSDate date];

// execute save data
[data saveAsyncWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
    if (succeeded) {
        // data updated, show success message
    } else {
        // failed to update data, show error message
    }
}];

MFData data // fetched data

// set data
data["name"] = "Beacon Two"
data["proximityUUID"] = "CB10023F-A318-3394-4199-A8730C7C1AEC"
data["major"] = 2
data["minor"] = 284
data["isActive"] = false
data["timestamp"] = NSDate()

// execute save data
data.saveAsync { (succeeded, error) in
    if succeeded {
        // data updated, show success message
    } else {
        // failed to update data, show error message
    }
}

Delete Data

To delete a data from the Mesosfer Cloud, use method deleteAsync:

[data deleteAsyncWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
    if (succeeded) {
        // data deleted, show success message
    } else {
        // failed to delete data, show error message
    }
}];
data.deleteAsync { (succeeded, error) in
    if succeeded {
        // data deleted, show success message
    } else {
        // failed to delete data, show error message
    } 
}