Magical Record: how to make programming with Core Data pleasant

Note: This article talks about the Core Data (via Magical Record) so I’m assuming that you are already familiar with it or at least you know the basics. If this it is not the case you can find very good tutorials on the Tim Roadely’s blog or on the Ray Wenderlich’s blog.

Note 2 (october 2012): The article has been updated to work with the MagicalRecord version 2.

If you have already work with the Core Data framework you probably found this hard to learn. When you start you have to understand what is the role of each components (NSManagedObject, NSManagedObjectContext, NSManagedObjectModel, NSPersistentStoreCoordinator, etc.) and how they work/communicate between them. Once you understand the principle, you notice that the code is not easy to do too. There is a tons of line of code to manage and it becomes a nightmare when you have to work in a multi-threading context. Here comes the very useful open-source project named Magical Record.

This post is intended to give an introduction to using the Magical Record project by showing and explaining you some code snippet extract from a little project which is available here.

Introduction

To define quickly Magical Record, it is a set of classes/categories build over Core Data framework. It provides a lot of convenient methods which make the Core Data programming easier because it is based over the Active Records pattern:

Active record is an approach to accessing data in a database. A database table or view is wrapped into a class; thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database; when an object is updated, the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view.

wikipedia

The Active Record fetching implemented in this project comes from the Ruby on Rails’ principles in order to provide a clean and light code. Before to show you how works Magical Records we are going to explain you how to “install’ it into your project.

Installation

Before to start, it is important to know how to install the Magical Record into your project. This is very simple! Follow these steps:

  1. Download the Magical Record from github here: https://github.com/magicalpanda/MagicalRecord
  2. Add the library into your project by dragging it into Xcode

  3. Link the CoreData Framework to your project (Project navigator > Targets > Build Phases > Link Binary With Libraries > + > CoreData.framework > Add)

  4. To make our life easier, add this import into yourprojectname-Prefix.pch:
    #import "CoreData+MagicalRecord.h"

    (This line makes the Magical Record classes available throughout all the project)

Make sure that the project compile correctly!
So now that you know how to install it, we are going to define a simple model to illustrate the Magical Record features.

Defining Our Model

As part of this article we need to create a model to illustrate the operations provide by the Magical Records project. The model that we are going to build is simple entity named “Person” with an age, firstname and lastname as attributes.

Let’s define the model by following these steps:

  1. Create a new model named TestModel (File > New File… > Core Data > Data Model) [image shadow=”true” lightbox=”true” size=”small” align=”center” title=”XCode 4.2″ caption=”Create TestModel.xcodedatamodel”]http://yannickloriot.com/wp-content/uploads/2012/03/Create-TestModel.xcodedatamodel.png[/image]
  2. Add a new Entity named Person (Add Entity)
  3. Add the attributes age (Integer16), firstname (String) and lastname (String)

  4. Generate the NSManagedObject (Editor > Create NSManagedObject Subclass… > Create) subclass to allow us to work easily with our entities

We now have our model and we can work with the Core Data framework and more specifically the Magical Record.

Working with Magical Record

In this part we are going to explain you how to work with the Core Data framework by using the Magical Record project.

Initializing and Clearing Core Data

If you have already made projects which using Core Data you must certainly remember that it requires a lot of boilerplate code to work it and especially during the initialization. A little reminder:

// In the AppDelegate.h
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;

// In the AppDelegate.m
- (void)applicationWillTerminate:(UIApplication *)application {
  // Saves changes in the application's managed object context before the application terminates.
  [self saveContext];
}

- (void)saveContext {
  NSError *error = nil;
  NSManagedObjectContext *managedObjectContext = self.managedObjectContext;

  if (managedObjectContext != nil) {
    if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
      NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
      abort();
    }
  }
}

#pragma mark - Core Data stack

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext {
  if (__managedObjectContext != nil) {
    return __managedObjectContext;
  }

  NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    
  if (coordinator != nil) {
    __managedObjectContext = [[NSManagedObjectContext alloc] init];
    [__managedObjectContext setPersistentStoreCoordinator:coordinator];
  }

  return __managedObjectContext;
}

/**
 Returns the managed object model for the application.
 If the model doesn't already exist, it is created from the application's model.
 */
- (NSManagedObjectModel *)managedObjectModel {
  if (__managedObjectModel != nil) {
    return __managedObjectModel;
  }

  NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"blaba" withExtension:@"momd"];
  __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    
  return __managedObjectModel;
}

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
  if (__persistentStoreCoordinator != nil) {
    return __persistentStoreCoordinator;
  }

  NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"blaba.sqlite"];

  NSError *error = nil;
  __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    
  if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
  }    

  return __persistentStoreCoordinator;
}

#pragma mark - Application's Documents directory

/**
 Returns the URL to the application's Documents directory.
 */
- (NSURL *)applicationDocumentsDirectory {
  return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

That does not really want to delve into it. However, this code is something simple because it just instantiate Core Data with its model and the data layer.
Now the same code as above using the Magical Record addition:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [MagicalRecord setupCoreDataStackWithStoreNamed:@"MyDatabase.sqlite"];
  // ...
  return YES;
}

- (void)applicationWillTerminate:(UIApplication *)application {
  [MagicalRecord cleanUp];
}

It looks more readable, no?! These 2 lines will hide all the previous boilerplate by creating the NSPersistentStoreCoordinator (linked to the “MyDatabase” sqlite database), the NSManagedObjectModel and the NSManagedObjectContext.

Persisting Entities

Now that we have initialized the Core Data components using Magical Record, we are ready to persist data to the database. From inside a controller (or elsewhere), this is pretty easy:

- (void)persistNewPersonWithFirstname:(NSString *)firstname lastname:(NSString *)lastname age:(NSNumber *)age {
  // Get the local context
  NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];

  // Create a new Person in the current thread context
  Person *person   = [Person MR_createInContext:localContext];
  person.firstname = firstname;
  person.lastname  = lastname;
  person.age       = age;

  // Save the modification in the local context
  // With MagicalRecords 2.0.8 or newer you should use the MR_saveNestedContexts
  [localContext MR_save];
}

As you can see we Magical Record provides a static method for the NSManagedObject which allow us to create a new entity in a very convenient way. In this example, I willingly retrieve the local managed context because if you work in a multi-threaded environment it will avoid inconsistent/concurrent errors. To finish, we don’t forget to save the person into the database!

Fetching Entities

Fetching an entity back out of the database is even easier. We can take advantage of the useful MR_find, MR_findAll and MR_findFirst methods to easily fetch objects based on multiple conditions and sorts:

// Query to find all the persons store into the database
NSArray *persons = [Person MR_findAll];

// Query to find all the persons store into the database order by their 'firstname'
NSArray *personsSorted = [Person MR_findAllSortedBy:@"firstname" ascending:YES];

// Query to find all the persons store into the database which have 25 years old
NSArray *personsWhoHave22 = [Person MR_findByAttribute:@"age" withValue:[NSNumber numberWithInt:25]];

// Query to find the first person store into the databe
Person *person = [Person MR_findFirst];

It exists a lot of other convenient methods to retrieve the data, I invite you to check the API.

Updating an Entity

Once you have fetched an object from Core Data, updating it is easy.

- (void)updateAge:(NSNumber *)newAge ofPersonWithFirstname:(NSString *)firstname lastname:(NSString *)lastname {
  // Get the local context
  NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];

  // Build the predicate to find the person sought
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"firstname ==[c] %@ AND lastname ==[c] %@", firstname, lastname];
  Person *personFounded  = [Person MR_findFirstWithPredicate:predicate inContext:localContext];

  // If a person was founded
  if (personFounded) {
    // Update its age
    personFounded.age   = newAge;

    // Save the modification in the local context
    // With MagicalRecords 2.0.8 or newer you should use the MR_saveNestedContexts
    [localContext MR_save];
  }
}

Updating an object involves just three steps:

  1. Fetching data from the Core Data as seen before
  2. Modifying the object
  3. Saving the entity on the local object context

Deleting an Entity

Deleting data is very similar, it just requires to call the MR_delete entity method:

- (void)deleteFirstPersonWithFirstname:(NSString *)firstname {
  // Get the local context
  NSManagedObjectContext *localContext= [NSManagedObjectContext MR_contextForCurrentThread];

  // Retrieve the first person who have the given firstname
  Person *personFounded = [Person MR_findFirstByAttribute:@"firstname" withValue:firstname inContext:localContext];

  if (personFounded) {
    // Delete the person found
    [personFounded MR_deleteInContext:localContext];

    // Save the modification in the local context
    // With MagicalRecords 2.0.8 or newer you should use the MR_saveNestedContexts
    [localContext MR_save];
  }
}

As always, remember to save the context.

Summary

With Magical Record, you can focus on your entities and how they’re useful in your application and do not worry about the Core Data boilerplate code needed. This is because Magical Record hides a lot lower mechanism to developers and it is based over the Active Record Pattern which is a more convenient way (in my opinion) to work with datas.

And even though Magical Record revolves around a simple concept, it’s incredibly powerful, in particular in a multi-threaded environment by providing a high abstraction level to avoid the headache.

Before to finish you can find here an example project which summarizes what has been said in this article.

I hope it will help you. If you have any question, contact me or leave a comment!

References

For further study or clarify some points you can follow these links:

1 Star2 Stars3 Stars4 Stars5 Stars (29 votes, average: 4.62 out of 5)
Loading...
Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInShare on RedditPin on Pinterest

45 comments

Time limit is exhausted. Please reload CAPTCHA.

  1. Olaf · March 31, 2012

    Thanks for the note about “localContext”, couldn’t wrap my head around this

  2. Pingback: MagicalRecord: how to make programming with Core Data pleasant ... | Core data | Scoop.it
  3. Pingback: MagicalRecord: how to make programming with Core Data pleasant ... | iOS Dev | Scoop.it
  4. Pingback: Tutorial: The Basics Of Using The Magical Record Core Data Library | WebScriptPlus.com
  5. Pingback: Core Data Helpers « Under The Bridge
  6. sundialSoft · March 31, 2012

    re: “Add the library into your project by dragging it into Xcode” you should say how to do this.
    eg: In XCode 4 With the library project closed you drag the project from Finder into your project.
    ow whatever specific instructions should be used for this project.

    I found this to be difficult the first few libraries I tried to use.

    • James · March 31, 2012

      Maybe you should be reading articles about the basics of Xcode instead.

  7. Pingback: Tutorial: The Basics Of Using The Magical Record Core Data Library | iPhone, iOS 5, iPad SDK Development Tutorial and Programming Tips
  8. Smart · March 31, 2012

    Hi,
    I am new to Obj-C and start learning a few months ago. My question is whether MR can add/remove fields(columns) in an entity or not. Thanks

  9. Andrew · March 31, 2012

    Nice example. But try to add new version on you model 🙂 TestModel.xcdatamodeld i aded 3 and after last one i get

    [MagicalRecordHelpers defaultErrorHandler:](0x20cc4) Error: {
    NSPersistenceFrameworkVersion = 386;
    NSStoreModelVersionHashes = {
    Person = ;
    };
    NSStoreModelVersionHashesVersion = 3;
    NSStoreModelVersionIdentifiers = (
    “”
    );
    NSStoreType = SQLite;
    NSStoreUUID = “190AD4BF-BA1E-466B-9297-C87DB6732A04”;
    “_NSAutoVacuumLevel” = 2;
    }

    Som what to do with this type of error?

    • cell · March 31, 2012

      Andrew – any luck with versioning of of your model? I’m stuck with same error, despite the magical manual saying the problem of versioning should be gone… thanks!

      • cell · March 31, 2012

        the answer is… 😉
        to change the data stack init method in AppDelegate to [MagicalRecord setupAutoMigratingCoreDataStack];

  10. Pingback: Last weeks iOS resources | E.V.I.C.T. B.V.
  11. newbiee_coredata · March 31, 2012

    hi,
    i user magical record in my current application.
    i follow instruction which You mention, but my database.sqlite is not get copied in document directory.

    please help me , i am stuck here.

    Thanks,

    • newbiee_coredata · March 31, 2012

      hi,
      i got my answer
      mydatabase.sqlite file is stored in library instead of document directory.

      thanks,

  12. Pingback: iphone开发资源汇总 | tomorrow also bad
  13. Stan · March 31, 2012

    hi,
    Thanks for sharing this post.
    I have a little problem with relationship.
    I use a tableviewcontroller with a NSFetchedResultsController
    self.clientFRC = [Client MR_fetchAllSortedBy:@”companyName”
    ascending:YES
    withPredicate:nil
    groupBy:nil
    delegate:self
    inContext:[NSManagedObjectContext MR_contextForCurrentThread]];

    My entity Client has a relationship with another one called Country.
    But when I try to display the country name from one item, it comes with a null value.
    Is there some thing to add to fetch the relationship?
    Thanks

  14. João · March 31, 2012

    Thanks alot for your work! This is the best thing i haver seen since the invention of BASIC! Why they make it so hard, who wants to work with data in such a low level? We cant make really good stuff if we keep concentrated so much on syntax and avoidable things. We really hit it on the heart! Keep doing that good stuff 😉

  15. Rolf Marsh · March 31, 2012

    I just downloaded MagicalRecord from GitHub, installed it following your instructions and everything was going well until I added the code for AppDelegate.m.

    There is no MagicalRecordHelpers in the version from GitHub… I did find it on the web in your “MagicalRecordTestProject”, but not in “MagicalPanda’s GitHub. I really don’t understand why they are different…

    How do I resolve this?

    • Yannick Loriot · March 31, 2012

      Hi,

      This article is not up to date yet because since I wrote it the version 2 has been released (I’ll updated it soon).
      So that’s why you don’t find the MagicalRecordHelpers into the git repo. Now the MagicalRecordHelpers and the MRCoreDataAction have been replaced with a single class named MagicalRecord.

      So now to setup your Core Data, you just need to replace the MagicalRecordHelpers by this: [MagicalRecord setupCoreDataStackWithStoreNamed:@"MyDatabase.sqlite"];

      • Rolf Marsh · March 31, 2012

        Thank you… kinda figured that was the correct way, but being a noob I needed confirmation.

  16. Gervasio Marchand · March 31, 2012

    I understand that it must be really hard to keep the articles updated… but just to save 3 hours of the next reader’s life, since MR 2.0.8, the method save doesn’t save it to the database… just to the current context, then… on your code, you should change

    [localContext MR_save];

    with

    [localContext MR_saveNestedContexts];

    so that it goes up to the root context and actually persists the changes into the sqlite database.

    just my two cents 😉 thanks for a great article about a great library

    • Yannick Loriot · March 31, 2012

      Yes that’s a bit hard to keep the articles up to date and sometimes (like here) I add some errors while trying to do this task.
      Thank you for your help, I change that now!

    • Thomas van der Heijden · March 31, 2012

      Just keep in mind that MR_saveNestedContexts is async and returns immediately while MR_save waits for the save to be complete before returning. This change is actually causing some issues since it’s not that easy anymore to do a nested sync save.

      • Gervasio Marchand · March 31, 2012

        Wow, that’s interesting, and definitely not expected… maybe we should use MR_saveNestedContextsErrorHandler:completion: ?

        just checked the source code at NSManagedObjectContext+MagicalSaves.m and it seems that just sending the completion block would do the trick

  17. Pingback: Losing My Mind Cause Fetching Core data always Return Fault - feed99
  18. Nate · March 31, 2012

    Hi guys,

    Has anyone noticed magical record being unreliable on saves? I am using the MR_saveNestedContexts command on my context, and no matter what method I use, or what kind of context, it usually persists nothing, and when I use a block, the completion handler fires, not the error handler, but still nothing is persisted! One of my objects is persisting, but never on the first run of the application, it fails to persist, then if I stop the app and start again it persists. What is going on? Could it be some other database code is interfering with the operation of magical record? I have a core data stack, and have other data access code in my app that I am attempting to phase out, could this be a problem? I am truly at a loss as to why saves seem to work at times, but mostly do not work, no matter what method I try, and there doesn’t seem to be any rhyme or reason to it. Any info would be greatly appreciated…

    • Nate · March 31, 2012

      Just an update, I finally realized that magical record was indeed saving, but it was saving to a different database! It creates the database exactly the same way that core data would but the database is created in a different location by default. Normally this would not be an issue, but when you’re transitioning from your own code to magical record, this could cause a great deal of confusion. If you’re going to use magical record, it’s a good idea to go ahead and remove all other database code and use it exclusively.

  19. Dexter Lohnes · March 31, 2012

    Nice little tutorial. Thanks much!

  20. Pingback: iOS Links – Resources I Collected While Learning iOSThe PHP Coder's Judy Chop | The PHP Coder's Judy Chop
  21. Pingback: iphone开发资源汇总 « T客网 ︱ Techpot
  22. Dean · March 31, 2012

    Really appreciate your tutorial. Very clear. The amount of boiler code for Core data was putting me off. But having seen how magic record reduces it so radically, I think I will give it a go. Thanks again for tutorial.

  23. Laurent Daudelin · March 31, 2012

    Nice tutorial. How about multiple threads? How to deal with background threads saving to the database while the UI is updated on the main thread?

  24. Pingback: iphone开发资源汇总 | 在路上
  25. Pingback: Magical Record: Disfruta trabajando con CoreData | Pedro Piñera
  26. Pingback: How do I setup Magical Record in a NSPersistentDocument? | BlogoSfera
  27. Pingback: SigF | Magical Record: Simple Core Data
  28. RPeres · March 31, 2012

    Just one thing, from the documentation:

    /// \discussion Replaced by \MR_saveToPersistentStoreAndWait

    So it’s not MR_saveNestedContexts.

    Great article

  29. Pingback: Taking the pain out of CoreData | Thought Repository
  30. cell · March 31, 2012

    Any idea how to fetch records in a way that some with null values in sorting columns, come as last?