Beginning with Couchbase Lite on iOS

Beginning with Couchbase Lite on iOS

This post is also available in: Portuguese (Brazil)

Here’s an introduction to using Couchbase Lite for data persistence on iOS, showing the minimum steps required to get this storage system integrated in your application. The text expects you have a bit of SQL background for comparison purposes, and should be a good start if you’re somewhat new to NoSQL technologies. Written to speed-up your learning of how all the parts fit together, hopefully after reading it carefully you’ll be more confident to work even if you’re just starting with the platform (at least I wish I had such explanation when I started). If you’re looking for more detailed information on a specific topic (e.g. reference API), I recommend searching in Couchbase’s official documentation.

Our goal with this introduction is the simplest possible: to store and retrieve one document containing our own contact information. Later, we proceed to storing multiple documents and see how to retrieve them back.

Despite simple, this should provide the theoretical basis for you to develop more complex scenarios. Start small, then improve.

Before starting, let’s see some important concepts from Couchbase NoSQL database:

  • The Manager: Top-level object that manages a collection of Couchbase Lite Database instances. You need to create a Manager instance before you can work with Couchbase Lite objects in your Application.
  • The Database:  a container and a namespace for documents, a scope for queries, and the source and target of replications.
  • A Document:  In a document database such as Couchbase Lite, the primary entity stored in a database is called a document instead of a “row” or “record”. A document may be linked to an Attachment.
  • Attachment: Attachments store data associated with a document, but are not part of the document’s JSON object. Their primary purpose is to make it efficient to store large binary data in a document.
  • The Views: I like to think of a view as a denormalized table from the SQL world (dynamically generated from JSON objects). A View is a persistent index of documents in a database, which you then query to find data. Couchbase Lite doesn’t have a query language like SQL; instead, it uses a technique called map/reduce to generate indexes (views) according to arbitrary app-defined criteria. The main component of a view (other than its name) is its map function. Views are persistent, and need to be updated (incrementally) whenever documents change, so having large numbers of them can be expensive. Instead, it’s better to have a smaller number of views that can be queried in interesting ways.
  • A Query: is the action of looking up results from a view’s index. In Couchbase Lite, queries are objects of the Query class. To perform a query you create one of these, customise its properties (such as the key range or the maximum number of rows) and then run it. The result is a QueryEnumerator, which provides a list of QueryRowobjects, each one describing one row from the view’s index.

With these concepts in mind, using Couchbase Lite boils down to the following steps:

  1. Add the library dependencies to the project
  2. Create a manager
  3. Create a database object
  4. Model your objects, save and retrieve them.
  5. Implement views, queries and save / delete methods.

Let’s get right into it:

 

Step 1 – Add the library dependencies to the project

Download and include the CouchbaseLite framework to the project following the usual procedures. The code illustrated here is based on Couchbase Lite release version 1.0.2. You will also make sure you have the following Linked Frameworks and Libraries in your project:

  • CFNetwork.framework
  • Security.framework
  • SystemConfiguration.framework
  • libsqlite3.dylib
  • libz.dylib

Then it’s time to instantiate our manager + database (step 2 and 3).

 

Step 2 & 3 : Create manager + database objects

This is normally stored as singleton as this object will be referenced often (check out official Couchbase code examples).

 

Step 4 – Model your objects, save and retrieve them.

Now to object modelling. If you have an existing project where you want to use Couchbase Lite, chances are, your objects are already modelled in native objects (“Model” as in Model View Controller). The last step then would be to map these objects in a form that can be saved to the database. And that form is, you guessed, a “Document”.

You can create a document with or without giving it an ID. If you don’t need or want to define your own ID, call theDatabase method createDocument, and the ID will be generated randomly in the form of a Universally Unique ID (UUID), which looks like a string of hex digits.

So we have basically two options to create it: [database createDocument] or [database documentWithID], where in the second a new document will be generated if not yet existent.

In our use-case (store own contact information), it’s better to use the username to create a unique id, since we will have only one of such document for each user (which should be unique on the system to this user). After instantiating the document object, we may add properties to it, e.g., like this:

Note that I inserted a “type” field there. Here’s why: In NoSQL, there is no concept of “tables” as we are used to in SQL. It is however a common pattern to use the document’s “type” property’s to achieve the same effect. Whereas in SQL we would structure/separate our data with tables, in Couchbase we can sort the data using the document’s “type” property when we later perform a Query. Of course, “type” is just a convention, you can name it whatever you want.

Couchbase Lite supports the typical database “CRUD” operations on documents: Create, Read, Update, Delete. However, there’s an alternative to manipulate our document:

In iOS, Couchbase Lite SDK already provides a mapper between native objects and CouchBase Lite Documents. At the time of this writing, it happens by using an abstract class called “CBLModel”. From the documentation:

CBLModel: Generic model class for CouchbaseLite documents. There’s a 1::1 mapping between these and CBLDocuments; call +modelForDocument: to get (or create) a model object for a document, and .document to get the document of a model. You should subclass this and declare properties in the subclass’s @interface. As with NSManagedObject, you don’t need to implement their accessor methods or declare instance variables; simply note them as ‘@dynamic’ in the class @implementation. The property value will automatically be fetched from or stored to the document, using the same name.

That basically means, that we don’t even have to parse objects to JSON and vice-versa for storage. How handy is that! *

As you see, either you can work with a document directly (CBLDocument), or via a model (CBLModel), or both. A document itself may therefore come as a result from a query, from a CBLModel object, or from database methods (e.g.: [database existingDocumentWithID]).

So let’s get back to business. Since we want to store our contact information, we create a class “ContactInfoModel”, inheriting from CBLModel, to work with the data that should be stored:

Let’s go ahead and implement methods to create our document using CBLModel. Now our model class looks like this:

The header file:

Now we can already store our contact information:

Unless we set the “autosaves” property of the CBLModel to true, objects aren’t immediately saved to the database when its properties change, so we manually did that with the [self save: &error] method after we changed its properties.

But, remember that we wanted to create the document with our own ID? Let’s add a method to accomplish that:

Calling the above method twice with the same “thisUserId” will return the same document.

Note that in this example (by creating the document’s unique id ourselves) , the “user_id” information is now duplicated: it is found in the user_id property, as well as it is our Document’s ID. But for the sake of explanation, I’ll leave that field so we can use it during the explanation of queries.

We are now creating, saving and retrieving documents (by their ID). But what if we have multiple documents? Or multiple document types? How to retrieve them back? Let’s meet the Couchbase’s Query system to help on that.\

 

Step 5 – Implement views, queries and save / delete methods.

In Couchbase lite, querying feels more like a more “prepared” and “divided” process when compared to SQL.

Here’s a high level view how data is mapped from the database to your native object:

The Database has Views. Views can be “queried”. Query’s responses can be post-processed (e.g.: filtered, ordered, summed). So it’s not like a one SQL statement-does-it-all with Joins() and Counts().

For this reason, in the beginning, the development can feel a bit backward when coming from SQL, where you just have the data and throw queries at it, because here we need to first create “views”, and just then the queries. But in the end it’s the same effect: you’re still just fetching data from the database. Just in a different way.

I like to think of a View as a function (much like what is known as Procedures, Triggers and Modules in relational databases) which is triggered every time a document is inserted, as a preparation of this data for efficient querying.

Without further ado, let’s create a method to get contactData based on username (the “user_id” property), first by creating a View:

We create a CBLView by calling the database’s method “viewNamed“. Using CBLView’s “setMapBlock”, every document which has property “type” set to “contactInfo” will return two parameters: the user_id, followed by the contact data.

What this code does is, it “installs” this view/map “procedure” to the database, so that it is run for every document.

So no matter how your document’s schema looks like, if it does have a “type: contactInfo” property at its root, they will be returned from this function. From an SQL perspective, I like to think of this as it would be generating another “index table” with two “columns” (username, contactData) , containing each “row” , the values, like the following:

Screen Shot 2014-09-23 at 2.14.03 PM

(Before you even try, the girl’s phones are fake 🙂 )

Using the CBLView, we finally create a query using its “createQuery” method. With the CBLQuery object in scope, we have the chance to do some final filtering:

We set the query’s result key range only to the provided user parameter.

So if we now call the method above like this:

The return of the query will contain the subset of the information:

Screen Shot 2014-09-23 at 2.21.04 PM

Oh no! The girls are gone! Let’s fix that:

Screen Shot 2014-09-23 at 2.34.27 PM

Now that was easy.

As you see, the username variable matches the key in the emit() key/value pair’s generated index table.

What if we want to map the result back to our object model, make some changes, and save? No problem:

 

Conclusion

So that’s it. If you want to see running code with sample projects, check out Couchbase’s github projects:

http://developer.couchbase.com/mobile/develop/samples/samples/index.html

Their HelloCBL iOS project is a good starting place to see the basics in action – but it does not show how to work with models as described here. For code on that, check the more “advanced” samples.

In a future post, I will try to share my experiences with the Couchbase’s Sync Gateway, expanding on the example of this text – so stay tuned!

Best Regards!

  • hi ! thanks for you help.
    i have a question about query cause .
    i mean .in sql such as :select *from user where (sore >90 and per = ‘wade’) or due = ‘today’
    how query this line on couch???
    Thanks

    • Thiago Alencar

      You could do something like this: emit([doc.userName, doc.score], doc). This would emit the whole document.
      Then you set the keys: [“wade”, 76] and [“wade”, {}]

      But note this regarding emitting the whole document (from official documentation): “It’s not necessarily bad, but most of the time you shouldn’t do it. The benefit is that, by having the document’s properties right at hand when you process a query row, it can make querying a little bit faster (saving a trip to the database to load the document.) But the downside is that it makes the view index a lot larger, which can make querying slower. So whether it’s a net gain or loss depends on the specific use case.”

      • Thanks.
        as you said:set the keys: [“wade”, 76] and [“wade”, {}]
        this mean :sore > 76 and per = ‘wade’ ?
        if it’s true.so,how to add like “or” case ? (sql : select *from user where (…) or (..)) ?

      • CBLQuery *contactQuery = [self queryContactInfoWithPro:[NSNumber numberWithInteger: 75] StartUserOlder:[NSNumber numberWithInteger: 1] endOlder:[NSNumber numberWithInteger: 5]];

        model:
        {
        @property (assign) int userOlder; //(1~10)
        @property (assign) int progress; //(0~100)
        }

        i just want query the progress>75 and 1<userOlder<5
        but not query success:
        //1- createView
        CBLView * contactInfoView = [self.database viewNamed: @"contactDataByProgress"];
        [contactInfoView setMapBlock: MAPBLOCK({
        if ([doc[@"type"] isEqualToString: @"contacInfo"]) {
        if (doc[@"progress"])
        emit(doc[@"progress"], doc[@"userOlder"]);
        }
        }) version: @"6"];

        //2 – make the query
        CBLQuery* query = [contactInfoView createQuery];
        NSLog(@"Querying older: %@ ,%@", startOlder,endOlder);
        query.startKey = @[pro,startOlder];
        query.endKey = @[@{},endOlder];
        return query;

      • My demo project here:https://github.com/79144876/CouchbaseQuery (base on your website ~)

  • Hi

    Wondering what’s the earliest iOS SDK version Couchbase Lite version 1.x supports … Does it support iOS 6.x? Or, is the minimum supported SDK iOS 7.x?

    • Thiago Alencar

      Hello hi@hi.com

      “Versions greater than 1.0 of Couchbase Lite iOS lib requires iOS7”, check this link.