We live in a world where the first question we ask when we arrive somewhere is “What’s your WiFi password?”.

Every one of us has found himself in a situation where we couldn’t connect to the internet. The reason for that could be that we spent all of the data in our data plan or just simply that there is no cell coverage in a specific area. At that point we are left to the mercy of developers — does the app work without an internet connection or are we stuck with the loading indicator. Every decent app should and must be able to work offline because otherwise — it just plain sucks! A technical term for that is persisting data.

For us iOS developers Apple provides 3 ways to do just that:

NSUserDefaults - A key-value dictionary that can store basic datatypes. It is super simple and it’s used widely for storing small amounts of data such as YES/NO values for specific user preferences. It’s not convenient for larger amounts of data mainly because it doesn’t support querying and it’s restriced to a limited set of objects.

NSCoding Protocol - Every object that conforms to the NSCoding protocol can be stored. Basically, every class for which the objects need to be stored must implement 2 methods:

    
- (void)encodeWithCoder:(NSCoder *)encoder
- (id)initWithCoder:(NSCoder *)decoder
    

This approach is still a bit slow, there is no querying and the implementations need to be written by hand.

CoreData - The king of data persistence. It provides tools for complex data models, querying, undo, automatic migrations and on top of that — it’s fast. It’s like NSCoding on steroids. Sounds too good to be true? It’s because it is. Every developer that ever worked with core data faced some kind of problems. It’s so big and complex that people even wrote books on it . CoreData was on the throne of data persistence in iOS apps since it was first released in 2007. But it’s 2014 now and we have a challenger waving his thread-safety sword and claiming that he’s ready to take over the throne - Realm! Is the winter coming for CoreData? We decided to integrate Realm into Basket and check that for ourselves.

Realm is the first database built from scratch for mobile devices. It’s fast and simple, just how we like it. Data is directly exposed trough objects and you can query it by code. It’s incredibly simple to integrate in your existing project and even simpler to use. It’s simplicity lies in the fact that you only need 3 classes to start persisting data:

RLMObject - represents your data. You can persist data by simply sublassing RMLObject and adding properties you want to be persisted.

RLMArray - the primary container class in Realm. It’s used for one-to-many relationships and for storing query results. It’s main difference from NSArray is that RLMArray can only store objects of one class.

RLMRealm - is basically your database. It contains information about the persistent store and it’s in charge of reading and writing data from it. You can use the default Realm or create one at the path you specify. The default Realm has the option to be used in-memory only, without data persistence.

Before reading further please read the latest version of the docs.

Installation is pretty straightforward and takes less than a minute to complete (use Pods bro!). Just add

    
pod 'Realm'
    

to your Podfile and run

    
pod install
    

Let’s create some demo objects! Since we’re making baskets, why not use them. A simplified version could look something like this:

    
//Basket.h
#import <Realm/Realm.h>

@interface Basket : RLMObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *shortDescription;
@property (nonatomic, strong) NSDate *dateCreated;

@end

//Basket.m
@implementation Basket
@end  
    

Looks good so far. Since the classes don’t need to be auto-generated every time we change them, we can add methods to the them without having to use categories.

We need a way to display the baskets. Subclass the UITableViewController and fill up the .m file with boilerplate code provided in the Realm TableView example.

    
//RealmDemoViewController.h
#import "RealmDemoViewController.h"
#import <Realm/Realm.h>
#import "Basket.h"

@interface RealmDemoViewController ()

@property (nonatomic, strong) RLMArray *baskets;
@property (nonatomic, strong) RLMNotificationToken *notification;

@end

@implementation RealmDemoViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    self.notification = [RLMRealm.defaultRealm addNotificationBlock:^(NSString *note, RLMRealm *realm) {
        [weakSelf reloadData];
    }];
    [self reloadData];
    
    self.navigationItem.rightBarButtonItem =
    [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                                                  target:self
                                                  action:@selector(add)];
}

- (void)add
{
    RLMRealm *realm = RLMRealm.defaultRealm;
    [realm beginWriteTransaction];
    Basket *basketToAdd = [[Basket alloc] init];
    basketToAdd.name = @"Demo basket";
    basketToAdd.dateCreated = [NSDate date];
    [realm addObject:basketToAdd];
    [realm commitWriteTransaction];
}

- (void)reloadData{
    self.baskets = [[Basket allObjects] arraySortedByProperty:@"dateCreated" ascending:YES];
    [self.tableView reloadData];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.baskets.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    // Set the data for this cell:
    Basket *basket = [self.baskets objectAtIndex:indexPath.row];
    cell.textLabel.text = basket.name;
    cell.detailTextLabel.text = basket.shortDescription;
    
    return cell;
}

@end
    

We’re ready to go. Run the app and hit the add button!

    
*** Terminating app due to uncaught exception 'RLMException', reason: 'No value or default value specified for shortDescription property'
    

Since we didn’t provide the default value for the shortDescription property, Realm is not allowing us to create the object. Let’s override the required method and provide some defaults:

    
//Basket.m
+ (NSDictionary *)defaultPropertyValues
{
    return @{@"name" : @"",
             @"shortDescription" : @"",
             @"dateCreated" : [NSDate date]};
}
    

The code runs smoothly now.

If you’re like me you probably don’t want to bother with writing this for every class you want to persist and having to remember to modify it whenever the class properties change, which to be honest, happens quite often during development. A quick test reveals that only properties of type NSString, NSDate and NSData need to provide default values. Let’s make a RLMObject subclass that will implement the required method and determine the default values in runtime. After that we can simply subclass it and have our default values auto filled.

    
//AutoDefaultsRMLObject.h
#import <Realm/Realm.h>

@interface AutoDefaultsRMLObject : RLMObject
@end

//Categories for easier default value handling
@interface NSString (Defaults)

+ (NSString *)defaultString;
- (BOOL)isDefault;

@end

@interface NSDate (Defaults)

+ (NSDate *)defaultDate;
- (BOOL)isDefault;

@end

@interface NSData (Defaults)

+ (NSData *)defaultData;
- (BOOL)isDefault;

@end

//AutoDefaultsRMLObject.m
#import "AutoDefaultsRMLObject.h"
#import <objc/runtime.h>

@implementation AutoDefaultsRMLObject

+ (NSDictionary *)defaultPropertyValues
{
    NSMutableDictionary *defaultValues = [NSMutableDictionary dictionary];
    unsigned int numberOfProperties = 0;
    objc_property_t *propertyArray = class_copyPropertyList([self class], &numberOfProperties);
    //go trough the properties
    for (NSUInteger i = 0; i < numberOfProperties; i++)
    {
        objc_property_t property = propertyArray[i];
        //aquire name
        NSString *name = [[NSString alloc] initWithUTF8String:property_getName(property)];
        //find the type attribute
        unsigned int numOfAttributes;
        objc_property_attribute_t *propertyAttributes = property_copyAttributeList(property, &numOfAttributes);
        for ( unsigned int ai = 0; ai < numOfAttributes; ai++ ) {
            switch (propertyAttributes[ai].name[0]) {
                    //type of the property
                case 'T':
                {
                    //add the default value
                    NSString *propertyType = [[NSString alloc] initWithUTF8String:propertyAttributes[ai].value];
                    if([propertyType isEqualToString:@"@\"NSString\""]){
                        [defaultValues setObject:[NSString defaultString] forKey:name];
                    }else if([propertyType isEqualToString:@"@\"NSDate\""]){
                        [defaultValues setObject:[NSDate defaultDate] forKey:name];
                    }else if([propertyType isEqualToString:@"@\"NSData\""]){
                        [defaultValues setObject:[NSData defaultData] forKey:name];
                    }
                    break;
                }
                default:
                    break;
            }
        }
        free(propertyAttributes);
    }
    free(propertyArray);
    
    return defaultValues;
}

@end

#pragma mark - Defaults implementations

@implementation NSString (Defaults)

+ (NSString *)defaultString{
    return [NSString new];
}

- (BOOL)isDefault{
    return [self isEqualToString:[NSString defaultString]];
}

@end

@implementation NSDate (Defaults)

+ (NSDate *)defaultDate{
    return [NSDate dateWithTimeIntervalSince1970:0];
}

- (BOOL)isDefault{
    return [self isEqualToDate:[NSDate defaultDate]];
}

@end

@implementation NSData (Defaults)

+ (NSData *)defaultData{
    return [NSData new];
}

- (BOOL)isDefault{
    return [self isEqualToData:[NSData defaultData]];
}

@end
    

As you can see, categories are added so we can easily check if the values are default. We didn’t pick the current datetime as the default value for NSDate because we want the option of detecting whether the NSDate property was set or not. Change the Basket.h so that the Basket class subclasses AutoDefaultsRMLObject and try it out!

    
*** Terminating app due to uncaught exception 'Invalid sort column', reason: 'Column named 'dateCreated' not found.'  
    

Looks like Realm does not allow subclassing. Guess we’ll have to settle for a bit dirtier solution. Create a new class called DefaultsHandler and move all of the functionality there, but this time as a class method that takes Class as an argument. The defaultPropertyValues method in the Basket class now looks like this:

    
+ (NSDictionary *)defaultPropertyValues
{
    NSMutableDictionary *defaults = [DefaultsHandler defaultValuesForClass:[self class]];
    [defaults setObject:[NSDate date] forKey:@"dateCreated"];
    return defaults;
}
    

Now we can use this for every RLMObject subclass we write. Because the method returns a mutable dictionary we can easily overwrite the values. You might be suprised, but that’s all you need to know to start using Realm!

Considering the last few paragraphs, it may seem that Realm is not a decent rival to CoreData, but the fact that subclassing was the only inconvenience we faced, and its simple to use interface makes it go head to head with CoreData.

While creating Basket, we decided to stick with CoreData mainly because it provides us with an easy way to do animated UITableView updates trough NSFetchedResultsController and we want Basket to have the full iOS “look and feel”. Additionaly, we have enough experience with CoreData not to get any gray hairs.

If your data models are not that complex or you just need a simple solution for caching stuff, Realm is what you’re looking for!