NanoStore: a NoSQL database for iOS


Nowadays saving data inside applications is one of the common goals in the development of applications. Tipically there are some resources on a web server that are fetched from a client which wants to save them locally and eventually send it back (with some changes) to the server.
If the server is storing data inside a NOSQL database (as MongoDB, OrientDB, etc..), this process is more efficient if server and client are speaking the same language (JSON) with the same resources representation (a document):

NoSql → Server — -> HTTP / JSON → Client NoSql → HTTP / JSON → Server → NoSql

The middle part of this simple chain is where persistence on mobile happens: typically, clients want to save offline server data.
The base tool for persistence in iOS is SQLite, which is a fine tool but it’s very different from the language of the server: in a base scenario you DON’T wants to create tables and indexes or write SQL, join, etc.. what you want is to save objects and retrieve this objects later.
The purpose of NanoStore is exactly to simplify the operations of saving and retrieving, with a NOSQL API inside an iOS application.
For the installation this is the project on github: https://github.com/tciuro/NanoStore.

Let’s start to create the object we want to save and retrieve:

#import “NSFNanoObject.h”
@interface Book : NSFNanoObject
    #define kBookTitle @”title”
#define kBookAuthor @”author”
    @property (strong, nonatomic) NSString *title;
@property (strong, nonatomic) NSString *author;
@end

with this implementation file:

#import “Book.h”
@implementation Book
@synthesize title, author;
- (id)initNanoObjectFromDictionaryRepresentation:(NSDictionary *)theDictionary forKey:(NSString *)aKey store:(NSFNanoStore *)theStore{
if (self = [super initNanoObjectFromDictionaryRepresentation:
theDictionary forKey:aKey store:theStore]){
if (theDictionary != nil){
self.title = [theDictionary objectForKey: kBookTitle];
self.author = [theDictionary objectForKey: kBookAuthor];
}
}
return self;
}
- (NSDictionary *)nanoObjectDictionaryRepresentation{
NSMutableDictionary *representation = [[NSMutableDictionary
alloc] init];
[representation setValue:title forKey:kBookTitle];
[representation setValue:author forKey:kBookAuthor];
return representation;
}
- (NSString *)debugDescription{
return [NSString stringWithFormat:@”BOOK — %@”,self.key];
}
- (id)rootObject{
returnself;
}


@end

So basically we have a simple Book object with two properties: title and author. The Book object extends NSFNanoObject which require to implement three methods:

  • (id)initNanoObjectFromDictionaryRepresentation: forKey: store:

this is the init method of a nano object, you must chain the init with the super implementation and then retrieve the properties of the object from the dictionary representation, which is the container of value’s properties.
The key is the unique identifier of the resource inside the database, you may specify a custom one or let NanoStore create a new one. Store is the instance of a database NanoStore (see later).

  • (NSDictionary *)nanoObjectDictionaryRepresentation

You must provide to NanoStore a NSDictionary with all the properties you want to save inside the database, this dictionary MUST be the cousin of the dictionary that is used inside of initNanoObjectFromDictionaryRepresentation, because NanoStore when recreates the objects from the database uses this dictionary in the previous init method.

  • (id)rootObject

This is used from NanoStore when it need to sort during a fetch operation.

Now that we have our object definition, we can save and retrieve books object. To save:

- (IBAction)newBook:(id)sender {
NSString *docs = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,NSUserDomainMask, YES) lastObject];
NSError *outError;
NSFNanoStore *nanoStore = [NSFNanoStore
createAndOpenStoreWithType:NSFPersistentStoreType path:[docs
stringByAppendingPathComponent:@”book.sqlite”] error:&outError];
  [nanoStore openWithError:&outError];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys
[NSString stringWithFormat:@”Title %d”,books.count],kBookTitle,
[NSString stringWithFormat:@”Author %d”,books.count],kBookAuthor, nil];
  Book *toSave = [[Book alloc]   
initNanoObjectFromDictionaryRepresentation:dict
forKey:nil store:nanoStore];
[nanoStore addObject:toSave error:&outError];
[nanoStore saveStoreAndReturnError:&outError];
[nanoStore closeWithError:&outError];
[self reloadData];
}

Assuming to have a books array which holds all the books used by the application, this method is:

  • opening a NanoStore instance with createAndOpenStoreWithType:path:error:

The available types are NSFPersistentStoreType for saving on disk (slow) and NSFMemoryStoreType for saving on ram (fast).

  • creating a Book object
  • calling addObject to add the Book object to the store
  • calling saveStoreAndReturnError for persisting data
  • calling closeWithError for closing the NanoStore instance
  • calling reloadData that will update the user view with the new Book saved offline

Let’s how reloadData retrieves the Book objects from NanoStore:

- (void) reloadData{
NSString *docs = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,NSUserDomainMask, YES) lastObject];
  NSError *outError;
NSFNanoStore *nanoStore = [NSFNanoStore
createAndOpenStoreWithType:NSFPersistentStoreType path:[docs
stringByAppendingPathComponent:@”book.sqlite”] error:&outError];
  [nanoStore openWithError:&outError];
NSFNanoSearch *search = [NSFNanoSearch
searchWithStore:nanoStore];
search.filterClass = NSStringFromClass([Book class]);
  NSFNanoSortDescriptor *sortByKey = [[NSFNanoSortDescriptor alloc]
initWithAttribute:@”title”ascending:NO];
search.sort = [NSArray arrayWithObject:sortByKey];
  NSMutableArray *results = [search 
searchObjectsWithReturnType:NSFReturnObjects error:&outError];
  books = [[NSMutableArray alloc] init];
  for (Book *book in results){
[books addObject:book];
}
  [tableView reloadData];
}

reloadData simply open the NanoStore instance like the previous method and then:

  • creates a NSFNanoSearch object which is responsible to perform a search inside NanoStore
  • defines a filterClass which allows to filter the results of the query with a particular type of object (in this case we want only the Book objects)
  • creates a NSFNanoSortDescriptor object to specify the sort of the resultant objects
  • performs the search with searchObjectsWithReturnType:error: which return the array of all the objects which match the search criteria
  • iterates trough the results and add every object to a new array
  • calls reloadData to a tableview assuming that there is a table which displays all the books

NB: this is very important, the result of searchObjectsWithReturnType:error: can be both a NSDictionary and a NSArray, the returning type is defined by the search instance: if you don’t specify a NSFNanoSortDescriptor object it returns an NSDictionary where the key are the unique identifier of the object and the value is the object; if you specify a sort descriptor, it returns an array with all the objects.

For the sake of clarity, in both the examples, the check of an error was removed, but in each step it’s better to check if *outError is null or contains an error.

In the end, with just a few rows of code, NanoStore allow you to save and retrieve objects, without write any table declaration or a complex configuration. It’s a very fine tool with good performance, obviously it depends on the needs of the application, but if you just need to save and retrieve data simple like these and you don’t need to do complex query I think that it’s the right tool.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.