Top 10 Core Data Tools and Libraries

We’ve compiled a list of the top 10 core data tools and libraries, making it even easier to work with Core Data. By Matthew Morey.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

7. GDCoreDataConcurrencyDebugging

Concurrency issues are some of the hardest things to debug in Core Data. The performBlock APIs help, but it’s still easy to make mistakes.

The open source project GDCoreDataConcurrencyDebugging can be added to your own projects to alert you via console messages when NSManagedObjects are accessed on the wrong thread or dispatch queue.

Below is an example of accessing an instance of NSManagedObject from the wrong context:

__block NSManagedObject *objectInContext1 = nil;

[context1 performBlockAndWait:^{

  objectInContext1 = [[NSManagedObject alloc] initWithEntity:entity 
                              insertIntoManagedObjectContext:context1];
  objectInContext1.name = @"test";

  NSError *saveError;
  if ([context1 save:&saveError] == NO) {

    NSLog(@"Error: %@", [saveError localizedDescription]);
  }
}];


// Invalid access
[context2 performBlockAndWait:^{
 NSString *name = objectInContext1.name;
}];

In the code above you’re trying to read name in context2 from an object that was originally created in context1.

If you were to run the above example using GDCoreDataConcurrencyDebugging you’d see the following console message that advises you of the problem:

2014-06-17 13:20:24.530 SampleApp[24222:60b] CoreData concurrency failure
Note: You should remove GDCoreDataConcurrencyDebugging from your app before you ship a build to the App Store as it does add a small amount of overhead that doesn’t need to be in your published app.

Core Data under iOS 8 and OS X Yosemite now has the ability to detect concurrency issues. To enable this new functionality you passing -com.apple.CoreData.ConcurrencyDebug 1 to your app on launch via Xcodeʼs Scheme Editor.

However, until you can phase out support for earlier OS versions in your app, GDCoreDataConcurrencyDebugging will keep you advised of concurrency issues during development.

The GDCoreDataConcurrencyDebugging README on Github is your best resource information on installing and using this tool.

6. CoreData-hs

CoreData-hs generates category methods to execute common fetch requests for all entities and properties in your Core Data Model. Creating these methods isn’t difficult, but it is time consuming — and every little bit of time saved coding is valuable!

For example, if your weather app had a view with the weather forecast and modeled each day’s forecast using a WFForecast entity with a timeStamp, temp, and summary attribute, CoreData-hs would create the following category for you:

#import <CoreData/CoreData.h>
#import <Foundation/Foundation.h>
@interface WFForecast (Fetcher)

+ (NSArray *)summaryIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)timeStampIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)timeStampIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)timeStampIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)timeStampIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)timeStampIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)timeStampIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)timeStampIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryIsLike:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryContains:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryMatches:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryBeginsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)summaryEndsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempIsLike:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempContains:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempMatches:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempBeginsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

+ (NSArray *)tempEndsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;

@end

As you can see there are a lot of methods generated! As an example, here’s the implementation generated for tempIsGreaterThan:inContext:sortDescriptors: error::

+ (NSArray *)tempIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock {
  NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"WFForecast"];
  [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"temp > %@", object]];
  [fetchRequest setSortDescriptors:sort];
  NSError *err = nil;
  NSArray *results = [context executeFetchRequest:fetchRequest error:&err];
  if(!results && errorBlock) {
    errorBlock(err);
    return nil;
  }
  return results;
}

Once the methods have been generated you can now use them to perform fetch requests with specific conditions. For example, if you need to fetch all WFForecast objects where the temperature is over 70° you can call tempIsGreaterThan:inContext:sortDescriptors:error: and simply pass in the target temperature as shown below:

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"temp" ascending:YES];
NSArray *results = [WFForecast tempIsGreaterThan:@(70)
                                       inContext:self.managedObjectContext
                                 sortDescriptors:@[sortDescriptor]
                                           error:^(NSError *error) {

  NSLog(@"Error: %@", [error localizedDescription]);
}];

You’ll get back an array of matching objects.

CoreData-hs is a lightweight utility that can save you time if you tend to generate a lot of these types of request by hand. For installation and usage instructions, consult the README on Github.

5. Core Data Editor

You can view and edit your app’s Core Data-based models from inside the GUI of Core Data Editor, which supports XML, binary and SQLite persistent store types. Beyond editing basic attributes, you can also edit and visualize data relationships. You can also use Mogenerator (discussed in item #2 below) with Core Data Editor to create your model code.

Core Data Editor is familiar with Apple’s schema and presents your data without the Z prefixes you might be familiar with if you’ve ever looked at the SQL files that Core Data generates. You can browse the contents of your app’s database in a nice table format. It also supports previewing of binary data such as pictures, and in-line editing of dates using a standard date picker:

Core Data Editor

If you need to create a seed file or just want to import data, Core Data Editor can take in a CSV file and turn it into persisted objects in Core Data as shown below:

Core Data Editor

To install Core Data Editor, download the free trial from the Thermal Core website. Uncompress the downloaded ZIP archive and move the Core Data Editor.app file to your Applications directory. The author of the app has also recently open sourced it if you want to find out how it works and make your own enhancements.

When you launch the app for the first time it will guide you through a short setup process. This process is optional but it will speed things up later if you specify, at a minimum, your iPhone Simulator directory and your Xcode derived data directory.

Note: Because you’re required to select your derived data and simulator directories in the GUI, you may run into trouble with default settings in OS X Lion and up that hide your Libraries folder.

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

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

Sometimes performing SQL queries directly on the underlying Core Data SQLite database can be helpful when debugging a knotty data issue. SQLite3 is a Terminal-based front-end to the SQLite library that comes installed on all Macs and should be familiar to those with extended database experience. If you don’t have extended database experience, this probably isn’t for you.

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

Change 7.1-64 from the above command to match the version of the simulator you’re using. {your app’s ID} is automatically generated by Xcode and uniquely identifies each app installation. There’s no easy way to find out which ID is yours. You can either add logging to your app when you create the core data stack, or look for the directory that was modified most recently – this will be the app you’re currently working on :]

The documents directory will contain a file with the extension sqlite, which is your app’s database file. For apps using Apple’s core data template, the filename will match your app’s name. Open this file using the SQLite3 program as follows (the example app here is called AddressBook, your filename will be different):

$ sqlite3 AddressBook.sqlite

You’ll see the following prompt appear in the console:

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

Now you’re ready to perform standard SQL queries against the database.

For example, to view the schema Core Data is using, execute the following command:

sqlite> select * from sqlite_master;

SQLite responds to your query with a textual listing of the tables in the schema as follows:

table|ZMDMPERSON|ZMDMPERSON|3|CREATE TABLE ZMDMPERSON ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZISNEW INTEGER, ZFIRSTNAME VARCHAR )
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)
sqlite>

The Z prefixes on all of the table columns are part of Core Data’s underlying use of SQLite. For analysis purposes, it’s safe to ignore them.

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

If you truly have a need to directly manipulate the SQLite database in a production application, you should forgo Core Data and use raw SQL access instead. There are several popular frameworks to help you manage SQL implementation in your apps, including FMDB and FCModel.

If you’re just analyzing your data, there’s nothing wrong with poking around the SQLite database file — just don’t modify its contents.

One example of using direct SQL to analyze your data is grouping and counting distinct attributes to see the diversity of your attributes.

For example, if you have a sample address book app and want to know how many of your contacts live in each city, you could execute the following command at the SQLite3 prompt:

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

SQLite would respond with the count of each distinct city in your address book database, as shown in the example below:

San Diego|23
Orlando|34
Houston|21

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

sqlite> .exit

For more information on SQLite3, view its man page by opening Terminal and executing the command man sqlite3.