Top 10 Core Data tools and open source libraries loved by developers

Top 10 Core Data tools and open source libraries loved by developers

Core Data is a great choice for storing and querying data in iOS and OSX applications. Not only can it reduce memory usage and improve performance, but it can also save you from writing a lot of unnecessary boilerplate code.

In addition, the Core Data API is very flexible and can be used in a variety of applications, all of which have different data storage needs.

However, this flexibility means that Core Data can sometimes be a little difficult to use. Even if you’re a Core Data expert, there are still common tasks you need to handle, and there are still many opportunities for mistakes.

Fortunately, there are a lot of great tools out there to help you out and make Core Data easier to use. We’ve rounded up 10 tools and open source libraries you should know and love.

Note: Even with all these great tools and libraries, you still need to have a good understanding of Core Data. If you need to get more experience with Core Data, download and check out our Getting Started Tutorial.

Also note that this article is primarily written in Objective-C, since most of the Core Data library is written in Objective-C. If you want to learn how to use Core Data with Swift, you can download and check out our book Core Data by Tutorials, which has been completely updated for iOS 8 and Swift!

#10. RestKit

RestKit is an Objective-C framework for interacting with RESTful web services. It provides a Core Data entity mapping engine that maps serialized response objects directly to managed objects.

The following code example shows how to set up RestKit to access the OpenWeatherMap API and map the JSON response from the /weather endpoint to a WFWeather managed object.

  1. - ( void )loadForecastData {
  2. RKManagedObjectStore *store = self.managedObjectStore;
  3.    
  4. // 1  
  5. RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@ "WFWeather"  
  6. inManagedObjectStore:store];
  7. [mapping addAttributeMappingsFromArray:@[@ "temp" , @ "pressure" , @ "humidity" ]];
  8.    
  9. // 2  
  10. NSIndexSet *statusCodeSet = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
  11. RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor
  12. responseDescriptorWithMapping:mapping
  13. method:RKRequestMethodGET
  14. pathPattern:@ "/data/2.5/weather"  
  15. keyPath:@ "main"  
  16. statusCodes:statusCodeSet];
  17.    
  18. // 3  
  19. NSURL *url = [NSURL URLWithString:
  20. [NSString stringWithFormat:@ "http://api.openweathermap.org/data/2.5/weather?q=Orlando" ]];
  21. NSURLRequest *request = [NSURLRequest requestWithURL:url];
  22. RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc]
  23. initWithRequest:request
  24. responseDescriptors:@[responseDescriptor]];
  25. operation.managedObjectCache = store.managedObjectCache;
  26. operation.managedObjectContext = store.mainQueueManagedObjectContext;
  27.    
  28. // 4  
  29. [operation setCompletionBlockWithSuccess:
  30. ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
  31. NSLog(@ "%@" ,mappingResult.array);
  32. [self.tableView reloadData];
  33. } failure:^(RKObjectRequestOperation *operation, NSError *error) {
  34. NSLog(@ "ERROR: %@" , [error localizedDescription]);
  35. }];
  36.    
  37. [operation start];
  38. }

Code breakdown:

1. First, create an RKEntityMapping object to tell RestKit how to map the API response to the WFWeather attributes.

2. Here, RKResponseDescriptor links the above response from /data/2.5/weather to the RKEntityMapping instance.

3. RKManagedObjectRequestOperation defines the operation to be performed. In this example, you request the weather for Orlando from the OpenWeatherMap API and point the response to the RKResponseDescriptor instance mentioned above.

4. Finally, perform the operation with the required success and failure blocks. When RestKit sees a response back that matches the defined RKResponseDescriptor, it will map the data directly to the WFWeather instance.

The above code requires no manual JSON parsing, checking for [NSNull null], manually creating Core Data entities, or anything else you have to do when connecting to an API. RestKit converts API responses into Core Data model objects with a simple mapping dictionary. It doesn’t get easier than this.

To learn how to install and use RestKit, download and follow our RestKit Tutorial.

#9. MMRecord

MMRecord is a block-based integration library that uses Core Data model configuration to automatically create and populate complete object graphs from API responses. It makes generating local objects from web service requests as simple as possible while it creates, retrieves, and populates NSManagedObjects instances for you in the background.

It makes generating local objects from web service requests as easy as it creates, retrieves, and populates NSManagedObjects instances in the background.

The following code block shows how to use MMRecord to make the same Orlando weather call and data mapping that you did in the RestKit example above:

  1. NSManagedObjectContext *context = [[MMDataManager sharedDataManager] managedObjectContext];
  2.    
  3. [WFWeather
  4. startPagedRequestWithURN:@ "data/2.5/weather?q=Orlando"  
  5. data:nil
  6. context:context
  7. domain:self
  8. resultBlock:^(NSArray *weather, ADNPageManager *pageManager, BOOL *requestNextPage) {
  9. NSLog(@ "Weather: %@" , weather);
  10. }
  11. failureBlock:^(NSError *error) {
  12. NSLog(@ "%@" , [error localizedDescription]);
  13. }];

Without having to write any complex networking code, or manually parse JSON responses, you’ve called an API and populated a Core Data managed object with the response data in just a few lines of code.

How does MMRecord know where to locate your object in the API response? Your managed object must be a subclass of MMRecord and then override keyPathForResponseObject as shown below:

  1. @interface WFWeather :MMRecord
  2. @property (nonatomic) float temp;
  3. @property (nonatomic) float pressure;
  4. @property (nonatomic) float humidity;
  5. @end
  6.    
  7. @implementation WFWeather
  8. @dynamic temp;
  9. @dynamic pressure;
  10. @dynamic humidity;
  11.    
  12. + (NSString *)keyPathForResponseObject {
  13. return @ "main" ;
  14. }
  15.    
  16. @end

keyPathForResponseObject returns a key path that specifies the location of the object relative to the root of the response object from the API. In this case, the key path is the main call to data/2.5/weather.

The magic doesn't end there - MMRecord requires you to create a server class that knows how to make calls to the API you've integrated. Thankfully, MMRecord comes with an example server class based on AFNetworking.

For information on configuring and using MMRecord, the documentation in the MMRecord Github repository is the best place to start.

#8. Magical Record

Inspired by Ruby on Rails' ActiveRecord system, it provides a set of classes and categories that support getting, inserting, and deleting a row of entities.

The following is a view of MagicalRecord in action:

  1. // Fetching NSArray *people = [Person MR_findAll];  
  2. // Creating Person *myPerson = [Person MR_createEntity];  
  3. // Deleting [myPerson MR_deleteEntity];  

MagicalRecord makes it very easy to set up a Core Data stack. Instead of using many lines of boilerplate code, you can set up a complete Core Data stack with just one method call in your AppDelegate file as shown below.

  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  2. // 1  
  3. [MagicalRecord setupCoreDataStackWithStoreNamed:@ "ExampleDatabase.sqlite" ];
  4.    
  5. return YES;
  6. }

In application:didFinishLaunchingWithOptions:, call setupCoreDataStackWithStoreNamed with the name of your SQLite file. This sets up instances of NSPersistentStoreCoordinator, NSManagedObjectModel, and NSManagedObjectContext so you’re ready to work with Core Data.

For more information on how to install and use MagicalRecord, see our MagicalRecord Tutorial.

#7. GDCoreDataConcurrencyDebugging

Concurrency issues are the hardest thing to debug in Core Data. The performBlock API can help, but it’s still easy to make mistakes.

You can add the open source project GDCoreDataConcurrencyDebugging to your own projects, which will alert you through console messages when NSManagedObjects are accessed on the wrong thread or dispatch queue.

The following is an example of accessing an NSManagedObject instance in the wrong context:

  1. __block NSManagedObject *objectInContext1 = nil;
  2.    
  3. [context1 performBlockAndWait:^{
  4.    
  5. objectInContext1 = [[NSManagedObject alloc] initWithEntity:entity
  6. insertIntoManagedObjectContext:context1];
  7. objectInContext1.name = @ "test" ;
  8.    
  9. NSError *saveError;
  10. if ([context1 save:&saveError] == NO) {
  11.    
  12. NSLog(@ "Error: %@" , [saveError localizedDescription]);
  13. }
  14. }];
  15.    
  16.    
  17. // Invalid access  
  18. [context2 performBlockAndWait:^{
  19. NSString *name = objectInContext1.name;
  20. }];

In the above code, you are trying to read name from an object in context2, which was originally created in context1.

If you run the above example using GDCoreDataConcurrencyDebugging, you will see the following console message informing you that a problem has occurred:

2014-06-17 13:20:24.530 SampleApp[24222:60b] CoreData concurrency failure

Note: When uploading your app to the App Store, you should remove GDCoreDataConcurrencyDebugging, which adds a small amount of overhead that doesn’t need to be present in the released app.

In iOS 8 and OS X Yosemite, Core Data now has the ability to detect concurrency issues. To enable this new feature, you can pass -com.apple.CoreData.ConcurrencyDebug 1 to your app at launch through Xcode’s Scheme Editor.

However, until you phase out support for earlier OS versions, GDCoreDataConcurrencyDebugging will keep you informed of concurrency issues during development.

The GDCoreDataConcurrencyDebugging README on Github is the best resource for installing and using this tool.

#6. CoreData-hs

CoreData-hs generates class methods to perform common fetch requests for all entities and attributes in your Core Data model. Creating these methods is not difficult, but it is time-consuming—and every bit of time saved when coding is valuable!

For example, if your weather app has a view for viewing the weather forecast and uses a WFForecast entity with timeStamp, temp and summary properties to simulate a daily forecast, CoreData-hs will create the following classes for you:

  1. #import #import @interface WFForecast (Fetcher)  
  2.    
  3. + (NSArray *)summaryIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  4.    
  5. + (NSArray *)summaryIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  6.    
  7. + (NSArray *)summaryIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  8.    
  9. + (NSArray *)summaryIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  10.    
  11. + (NSArray *)summaryIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  12.    
  13. + (NSArray *)summaryIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  14.    
  15. + (NSArray *)summaryIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  16.    
  17. + (NSArray *)tempIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  18.    
  19. + (NSArray *)tempIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  20.    
  21. + (NSArray *)tempIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  22.    
  23. + (NSArray *)tempIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  24.    
  25. + (NSArray *)tempIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  26.    
  27. + (NSArray *)tempIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  28.    
  29. + (NSArray *)tempIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  30.    
  31. + (NSArray *)timeStampIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  32.    
  33. + (NSArray *)timeStampIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  34.    
  35. + (NSArray *)timeStampIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  36.    
  37. + (NSArray *)timeStampIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  38.    
  39. + (NSArray *)timeStampIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  40.    
  41. + (NSArray *)timeStampIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  42.    
  43. + (NSArray *)timeStampIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  44.    
  45. + (NSArray *)summaryIsLike:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  46.    
  47. + (NSArray *)summaryContains:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  48.    
  49. + (NSArray *)summaryMatches:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  50.    
  51. + (NSArray *)summaryBeginsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  52.    
  53. + (NSArray *)summaryEndsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  54.    
  55. + (NSArray *)tempIsLike:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  56.    
  57. + (NSArray *)tempContains:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  58.    
  59. + (NSArray *)tempMatches:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  60.    
  61. + (NSArray *)tempBeginsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  62.    
  63. + (NSArray *)tempEndsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock;
  64.    
  65. @end

#p#

As you can see, a lot of methods were generated! Here is the generated implementation of tempIsGreaterThan:inContext:sortDescriptors: error: :

  1. + (NSArray *)tempIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:( void (^)(NSError *error))errorBlock {
  2. NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@ "WFForecast" ];
  3. [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@ "temp > %@" , object]];
  4. [fetchRequest setSortDescriptors:sort];
  5. NSError *err = nil;
  6. NSArray *results = [context executeFetchRequest:fetchRequest error:&err];
  7. if (!results && errorBlock) {
  8. errorBlock(err);
  9. return nil;
  10. }
  11. return results;
  12. }

Once the methods are generated, you can use them to get requests based on specific conditions. For example, to get all WFForecast objects with a temperature greater than 70°, you can call tempIsGreaterThan:inContext:sortDescriptors:error: and simply pass the target temperature, as shown below:

  1. NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@ "temp" ascending:YES];
  2. NSArray *results = [WFForecast tempIsGreaterThan:@(70)
  3. inContext:self.managedObjectContext
  4. sortDescriptors:@[sortDescriptor]
  5. error:^(NSError *error) {
  6.    
  7. NSLog(@ "Error: %@" , [error localizedDescription]);
  8. }];

You will get back a matching object with the data.

CoreData-hs is a lightweight tool that can save you time if you tend to generate a lot of these types of requests manually. For installation and usage instructions, see the README on Github.

#5. Core Data Editor

You can view and edit your app's Core Data-based data model in the Core Data Editor GUI, which supports XML, binary, and SQLite persistence storage types. In addition to editing basic properties, you can also edit and visualize data relationships. You can also use the Mogenerator tool (discussed in item #2) to create model code while using the Core Data Editor.

Core Data Editor follows Apple's pattern of displaying data without the Z prefix, which you might be familiar with if you've ever seen a SQL file generated by Core Data. You can browse the contents of your application's database in a nice tabular format. It also supports previewing binary data, such as images, and in-line editing of data with a standard date picker.

If you want to create a seed file or just want to import data, Core Data Editor can generate a CSV file and convert it into a persistent object in Core Data, as shown below:

To install Core Data Editor, download a free trial from the Thermal Core website. Unzip the downloaded ZIP archive and move the Core Data Editor.app file to your Applications directory. The author of this app recently made it open source, so you can check it out if you want to learn how it works and improve yourself.

The first time you launch the app, it will walk you through a brief setup process. This process is optional, but it will speed things up if you at least specify the iPhone Simulator directory and the directory for your Xcode archive data.

Note: Because you have to choose the exported data and simulator directories in the GUI, you may have problems using the OS X Lion default settings, which hide your Library folder.

In Mavericks OS X, you can correct this by going to the Finder's home directory, selecting View / Show View Options and checking Show Library Folder. In Lion and Mountain Lion OS X, the same thing can be accomplished by typing chflags nohidden ~/Library/ in Terminal.

More details about Core Data can be found on Thermal Core's website.

#4. SQLite3

Sometimes when debugging a tricky data problem, it helps to execute SQL queries directly on the underlying Core Data SQLite database. If you don’t have extensive database experience, this may not be for you.

To use SQLite3, first open Terminal and navigate to your app's Documents directory. Depending on your installation, the Documents directory will be something like ~/Library/Application Support/iPhone Simulator/7.1-64/Applications/{your app's ID}/Documents.

Change the 7.1-64 in the naming above to match the version of the simulator you are using. {your app's ID} is automatically generated by Xcode and uniquely identifies an installation of your app. There is no easy way to find out which ID is yours. You can add logging when you create the Core Data stack, or you can look for the directory that has been modified most recently - this will be the app you are currently working on.

The documents directory will contain a file with the extension sqlite. This is your application's database file. For applications using Apple's Core Data template, the file name should match the name of your application. Open this file with the SQLite3 program as shown below (the example application here is called AddressBook, your file name will be different):

1 $ sqlite3 AddressBook.sqlite

You will see the following prompt in the console:

  1. SQLite version 3.7.13   2012-07-17   17 : 46 : 21  
  2. Enter ".help"   for instructions
  3. Enter SQL statements terminated with a ";"  
  4. sqlite>

Now you are ready to execute standard SQL queries against the database.

For example, to see the schema used by Core Data, execute the following command:

  1. sqlite> select * from sqlite_master;

SQLite responds to your query with a list of text lists in the schema, like this:

  1. table|ZMDMPERSON|ZMDMPERSON|3|CREATE TABLE ZMDMPERSON ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZISNEW INTEGER, ZFIRSTNAME VARCHAR )
  2. table|Z_PRIMARYKEY|Z_PRIMARYKEY|4|CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER) table|Z_METADATA|Z_METADATA|5|CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB)
  3. sqlite>

All the Z prefixes in the list columns are part of the underlying SQLite used by Core Data. For the purpose of analysis, you can ignore them.

Note: You cannot write directly to a SQLite Core Data database. Apple can modify the underlying structure at any time.

If you really need to manipulate SQLite data directly in a production application, you should abandon Core Data and use raw SQL access. There are several popular frameworks that can help you manage the SQL implementation in your application, including FMDB and FCModel.

If you are just analyzing data, it is okay to access the SQLite database file everywhere - as long as you do not modify its contents. An example of using SQL directly to analyze data is to group and calculate different attributes to understand the differences in the attributes.

For example, if you have an address book application and want to know how many contacts live in each city, you can execute the following command at the SQLite3 prompt:

  1. SELECT t0.ZCITY, COUNT( t0.ZCITY ) FROM ZMDMPERSON t0 GROUP BY t0.ZCITY

SQLite would respond with data for each of the different cities in the address book database, as shown in the following example:

  1. San Diego|23
  2. Orlando|34
  3. Houston|21

To exit the SQLite3 terminal program, simply execute the following command:

  1. sqlite> .exit

To learn more about SQLite3, open a terminal to view the man page and execute the man sqlite3 command.

#3. MDMCoreData

MDMCoreData (disclaimer - I wrote this library!) is a collection of open source classes that make working with Core Data easier. It does not try to hide or abstract Core Data, but rather enforces best practices and reduces the amount of boilerplate code required. It is a much better alternative to the Xcode Core Data templates.

MDMCoreData consists of the following four classes:

  • MDMPersistenceController, a convenience controller that supports the creation of multiple child-managed object contexts to build an efficient Core Data stack. It has a built-in private managed object context that saves asynchronously to a SQLite store.

  • MDMFetchedResultsTableDataSource, get the ResultsController delegate and list data source.

  • MDMFetchedResultsCollectionDataSource, get the ResultsController delegate and collection data source.

  • NSManagedObject+MDMCoreDataAdditions, a managed object category that provides helper methods to eliminate boilerplate code, such as entity names.

One of the best features of MDMCoreData is that it comes with a Core Data-backed table data source - so you don't have to worry about implementing it yourself.

#p#

Instead of implementing all the methods required by the UITableViewDataSource and NSFetchedResultsControllerDelegate protocols, you can just set the table data source of the MDMFetchedResultsTableDataSource instance.

When instantiating the MDMFetchedResultsTableDataSource object, you simply pass in the table view and get a results controller:

  1. - ( void )viewDidLoad {
  2. [ super viewDidLoad];
  3.    
  4. self.tableDataSource = [[MDMFetchedResultsTableDataSource alloc] initWithTableView:self.tableView
  5. fetchedResultsController:[self fetchedResultsController]];
  6. self.tableDataSource.delegate = self;
  7. self.tableDataSource.reuseIdentifier = @ "WeatherForecastCell" ;
  8. self.tableView.dataSource = self.tableDataSource;
  9. }

MDMFetchedResultsTableDataSource has a delegate with two methods that must be implemented. One method configures the cells for your table:

  1. - ( void )dataSource:(MDMFetchedResultsTableDataSource *)dataSource configureCell:(id)cell
  2. withObject:(id)object {
  3. OWMForecast *forecast = object;
  4. UITableViewCell *tableCell = (UITableViewCell *)cell; tableCell.textLabel.text = forecast.summary; tableCell.detailTextLabel.text = forecast.date;
  5. }

The second method handles the deletion:

  1. - ( void )dataSource:(MDMFetchedResultsTableDataSource *)dataSource deleteObject:(id)object
  2. atIndexPath:(NSIndexPath *)indexPath {
  3. [self.persistenceController.managedObjectContext deleteObject:object];
  4. }

Implementing the two required MDMFetchedResultsTableDataSource methods is much simpler than implementing all the required methods of the table data source and obtaining the results controller protocols.

You can find more information about MDMCoreData in the MDMCoreData Github repository.

#2. Mogenerator

Because Core Data fully supports key/value coding (KVC) and key/value observing (KVO), there is no need to implement a custom NSManagedObject class. You can use setValue:forKey: and setValue:forKey: when reading and writing entities to your classes. But this often becomes complicated and difficult to debug because strings cannot be checked for correctness at compile time.

For example, if you have a Core Data entity called person, you can read and write properties like this:

  1. NSString *personName = [person valueForKey:@ "firstName" ];
  2. [person setValue:@ "Ned" forKey:@ "firstName" ];

The person object above is an NSManagedObject instance with a property called firstName. To read firstName, you use firstName as the valueForKey: keyword. Similarly, you can set the first name of a person object using setValue:forKey:.

A better approach is to use standard accessor methods or dot syntax. However, to do this you must implement a custom subclass of NSManagedObject for your entity. You can add model logic such as fetch requests and validation.

You may use Xcode's Create NSManagedObjectSubclass feature to quickly subclass a single entity. Although this is a shortcut, it adds extra overhead if your data model is large and can cause problems when changing the model.

Re-subclassing means clearing out all your custom model logic - which means you should create logic outside of your custom models. It fits into the general pattern of creating custom subclasses with managed object properties and custom model logic classes.

The command line tool Mogenerator automates these exact tasks. Two classes are generated for each Core Data entity. The first class is generated for machine consumption and is constantly overwritten when the model changes. The second class is generated for all your custom logic and is never overwritten.

Mogenerator has a range of other benefits, including the following:

  • There is no need to use NSNumber objects when reading/writing numeric properties.

  • Helper methods for handling settings

  • Helper methods for creating new entities

  • An Entity Recognition Method

Mogenerator can be installed from the DMG file available on the Mogenerator website, or by installing Mogenerator via Homebrew, open a terminal and execute the following command:

  1. brew install mogenerator

Once installed, use the cd command to change to your application's directory, then run Mogenerator from the terminal, like this:

  1. $ mogenerator -m MySampleApp/ExampleModel.xcdatamodeld -O MySampleApp/Model --template- var arc= true  

In the command above, you invoke Mogenerator with the -m option, followed by the location of the model. You can also specify the location of the generated classes with the -O option. When using ARC, you need to pass the --template-var arc=true option.

You can have Xcode run Mogenerator by creating a Run Script Build Phase. Build Phases are descriptions of tasks that Xcode must perform during a build.

To add a Build Phase, first select the target, select the Build Phases tab, and then select Editor / Add Build Phase / Add Run Script Build Phase from the menu.

Add the following code to the Shell script text area under the new Run Script, making sure to modify the mogenerator parameters to suit your project:

  1. if [ "${CONFIGURATION}" == "Debug" ]; then
  2. echo "Running Mogenerator"  
  3. mogenerator -m MySampleApp/ExampleModel.xcdatamodeld -O MySampleApp/Model --template- var arc= true  
  4. echo "Finished Mogenerator"  
  5. else echo "Skipping Mogenerator"  
  6. fi
The above run script will make Xcode run Mogenerator every time you run a debug build command. If the model has not changed, Mogenerator will do nothing and then exit.

Now that you have incorporated Mogenerator into your workflow for rapid subclassing, you should take advantage of its other features.

For example, instead of unwrapping the raw values ​​each time, you can just add the Value suffix to them, as shown in the following code snippet:

  1. // Without Mogenerator  
  2. if ([person.isFriend boolValue]) {
  3. // Do some work  
  4. }
  5.    
  6. //With Mogenerator  
  7. if (person.isFriendValue) {
  8. // Do some work  
  9. }

Because bools are stored as NSNumbers in Core Data, you must call boolValue on the person object before checking if the value is true. With Mogenerator, there is no extra step required because you can simply call isFriendValue.

If Mogenerator looks like a useful addition to your toolbox, you can find out more about Mogenerator in its Github repository.

#1. Instruments

Instruments is the go-to tool for investigating nearly any performance and memory issue in OS X and iOS—including Core Data issues. The other tools on this list provide a lot of automation and convenience, but Instruments is usually the first stop for investigating any problem or performance tweak.

The following figure shows the Time Profiler and Core Data templates, which are most useful for Core Data configuration.

The default Core Data template adds the optional Faults Instrument feature to help you tune and monitor your app’s performance by providing the following capabilities:

  • Core Data Fetches Instrument, which captures the number of fetches and the duration of fetch operations.

  • Core Data Cache Misses Instrument, which captures failure events that lead to cache misses.

  • Core Data Saves Instrument captures information about save events in the managed object context.

  • The Core Data Faults Instrument captures information about failure events that occur during lazy initialization of NSManagedObjects or related relationships.

This is a typical instruments profile for a Core Data application. You can see when fetch requests occur and how long they take, when and how save operations occur, and when the failure light appears.

To get more information about Instruments, download our tutorial: How to Use Instruments in Xcode.

Next step

Core Data is a very powerful framework, but it will have a lot of development overhead, but the tools and libraries in this article provide some methods to help you solve the overhead problem efficiently and effectively.

<<:  Java Thread Interview Questions

>>:  O2O companies face capital winter, 99% of angel investors face investment failure

Recommend

Python Quantitative Finance Practice by Wall Street Masters

Introduction to Wall Street Master-level Python Q...

Brand Marketing: Let posters arouse consumer power in 3 seconds

In response to the phenomenon that posters are th...

Interpretation of the user incentive system of Station B

This article will start from the perspective of B...

Steps to set up a Tik Tok account, essential tips for beginners!

In recent years, “Two Weibo and One Douyin” can b...

If we live long enough, will everyone eventually develop Alzheimer's?

November 26, 1901, Frankfurt, Germany. A young cl...