Top 10 Core Data Tools and Libraries

来源:互联网 发布:类似知否的高质量 编辑:程序博客网 时间:2024/06/10 10:10

Core Data is a great choice for persisting and querying data in your iOS and OSX apps. 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 extremely flexible, which allows it to be used in a myriad of apps, all with different data storage requirements.

However, this flexibility means that sometimes Core Data can be slightly difficult to work with. Even if you’re a Core Data guru, there’s still a lot of mundane tasks required, and a lot of room to make silly errors.

Luckily, there are a lot of great tools that can help you out and make Core Data significantly easier to work with. Here are our top 10 picks that you should know and love!

Note: Even with these great tools and libraries, you’ll still need a good understanding of Core Data to reap their benefits. If you need a some more experience with Core Data, check out our beginner tutorial.

Also note this article has an Objective-C focus since most Core Data libraries are written in Objective-C at the moment. If you want to learn how to use Core Data with Swift, check out our upcoming book Core Data by Tutorials, which is fully 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 code example below shows you how to set up RestKit to access the OpenWeatherMap API and map the JSON response of the /weather endpoint into a WFWeather managed object:

- (void)loadForecastData {  RKManagedObjectStore *store = self.managedObjectStore;   // 1  RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"WFWeather"                                                 inManagedObjectStore:store];  [mapping addAttributeMappingsFromArray:@[@"temp", @"pressure", @"humidity"]]// 2  NSIndexSet *statusCodeSet = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);  RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor                                              responseDescriptorWithMapping:mapping                                              method:RKRequestMethodGET                                              pathPattern:@"/data/2.5/weather"                                              keyPath:@"main"                                              statusCodes:statusCodeSet]// 3  NSURL *url = [NSURL URLWithString:                [NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/weather?q=Orlando"]];  NSURLRequest *request = [NSURLRequest requestWithURL:url];  RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc]                                                initWithRequest:request                                                responseDescriptors:@[responseDescriptor]];  operation.managedObjectCache = store.managedObjectCache;  operation.managedObjectContext = store.mainQueueManagedObjectContext;   // 4  [operation setCompletionBlockWithSuccess:   ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){     NSLog(@"%@",mappingResult.array);     [self.tableView reloadData];   } failure:^(RKObjectRequestOperation *operation, NSError *error) {     NSLog(@"ERROR: %@", [error localizedDescription]);   }][operation start];}

Here’s what’s going on in the code above:

  1. First, you create a RKEntityMapping object that tells RestKit how to map API responses to attributes of WFWeather.
  2. Here, RKResponseDescriptor ties responses from /data/2.5/weather to the RKEntityMapping instance above.
  3. RKManagedObjectRequestOperation defines which operation to execute. In this example, you request the weather in Orlando from the OpenWeatherMap API and point the response to the instance of RKResponseDescriptor noted above.
  4. Finally, you execute the operation with the requisite success and failure blocks. When RestKit sees a response coming back that matches the defined RKResponseDescriptor it will map the data directly into your instance of WFWeather.

In the code above there’s no need for manual JSON parsing, checking for [NSNull null], manual creation of Core Data entities, or any of the other routine things that one must do when connecting to an API. RestKit turns API responses into Core Data model objects via a simple mapping dictionary. It doesn’t get much easier than that.

To learn how to install and use RestKit, check out our Introduction to RestKit tutorial.

9. MMRecord

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

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

NSManagedObjectContext *context = [[MMDataManager sharedDataManager] managedObjectContext][WFWeather   startPagedRequestWithURN:@"data/2.5/weather?q=Orlando"                      data:nil                   context:context                    domain:self  resultBlock:^(NSArray *weather, ADNPageManager *pageManager, BOOL *requestNextPage) {    NSLog(@"Weather: %@", weather);  }  failureBlock:^(NSError *error) {    NSLog(@"%@", [error localizedDescription]);}];

Without writing any complicated networking code or manually parsing the JSON response, you’ve called an API and populated your Core Data managed objects with the response data in only a few lines of code.

How does MMRecord know how to locate your objects in the API response? Your managed objects must be subclasses of MMRecord and override keyPathForResponseObject as shown below:

@interface WFWeather : MMRecord@property (nonatomic) float temp;@property (nonatomic) float pressure;@property (nonatomic) float humidity;@end @implementation WFWeather@dynamic temp;@dynamic pressure;@dynamic humidity; + (NSString *)keyPathForResponseObject {    return @"main";} @end

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

It’s not all magic — MMRecord does require that you create a server class that knows how to make requests against the API you’re integrating against. Thankfully, MMRecord comes with sample AFNetworking-based server classes.

For complete information on setting up and using MMRecord, the readme on the MMRecord Github repository is the best place to start.

8. Magical Record

Modeled after Ruby on Rails’ ActiveRecord system, MagicalRecord provides a set of classes and categories that enable one-line entity fetch, insertion and deletion operations.

Here’s a view of MagicalRecord in operations:

// FetchingNSArray *people = [Person MR_findAll]// CreatingPerson *myPerson = [Person MR_createEntity]// Deleting[myPerson MR_deleteEntity];

MagicalRecord makes it easy to set up your Core Data stack. Instead of having many lines of boilerplate code, you can set up a full Core Data stack with only one method call in your AppDelegate file as follows:

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

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

Have a look at our MagicalRecord tutorial for further information on how to install and use MagicalRecord.

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 timeStamptemp, 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 yourApplications 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.

4. SQLite3

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:21Enter ".help" for instructionsEnter 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|23Orlando|34Houston|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.

3. MDMCoreData

MDMCoreData (disclaimer – this library is written by me!) is a collection of open source classes that make working with Core Data easier. It doesn’t try to hide or abstract Core Data, but instead enforces best practices and reduces the amount of boilerplate code required. It’s a better alternative than the Xcode Core Data Template.

MDMCoreData consists of the following four classes:

  • MDMPersistenceController – A handy controller that sets up an efficient Core Data stack with support for creating multiple child-managed object contexts. It has a built-in private managed object context that saves asynchronously to a SQLite store.
  • MDMFetchedResultsTableDataSource – Implements the fetched results controller delegate and a table data source.
  • MDMFetchedResultsCollectionDataSource – Implements the fetched results controller delegate and a collection data source.
  • NSManagedObject+MDMCoreDataAdditions – A category on managed objects providing helper methods for eliminating boilerplate code such as entity names.

One great feature of MDMCoreData is that it comes with a Core Data backed table data source — so you don’t have to worry about implementing one yourself.

Instead of implementing all the required methods in the UITableViewDataSource and NSFetchedResultsControllerDelegate protocol, you can just set your table’s data source to an instance of MDMFetchedResultsTableDataSource. When instantiating the MDMFetchedResultsTableDataSource object you simply pass in the table view and a fetched results controller:

- (void)viewDidLoad {  [super viewDidLoad];   self.tableDataSource = [[MDMFetchedResultsTableDataSource alloc] initWithTableView:self.tableView                                                        fetchedResultsController:[self fetchedResultsController]];  self.tableDataSource.delegate = self;  self.tableDataSource.reuseIdentifier = @"WeatherForecastCell";  self.tableView.dataSource = self.tableDataSource;}

MDMFetchedResultsTableDataSource does have a delegate, with two methods that must be implemented. One method configures the cell for your table:

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

The second method handles deletions:

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

It’s far easier to implement the two required methods of MDMFetchedResultsTableDataSource than to implement all of the methods required by the table data source and fetch results controller protocols.

You can find out more about MDMCoreData at the MDMCoreData Github repository.

2. Mogenerator

Since Core Data comes with full support for key-value coding (KVC) and key-value observing (KVO), there is no requirement to implement custom NSManagedObject classes. You can get by using setValue:forKey: and valueForKey: when reading or writing attributes on your entities. But this tends to be cumbersome and hard to debug since strings can’t be checked at compile time for correctness.

For example, if you had a person Core Data entity, you could read and write attributes like this:

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

The person object above is an instance of NSManagedObject with an attribute named firstName. To read firstName, you use valueForKey: with the key firstName. Similarly, to set the first name of a person object you can use 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 entities. This lets you add model logic such as fetch requests and validation.

You have probably used Xcode’s Create NSManagedObjectSubclass functionality to quickly create a subclass for a single entity. Although it’s a nice shortcut, it can create extra overhead if you have a large model and can cause you grief when your model changes.

Re-creating the subclass means wiping out all of your custom model logic — which means you should host that logic outside of your custom model. This lends itself to a common pattern of creating custom subclasses with managed object properties along with categories for custom model logic.

The command line tool Mogenerator automates these exact tasks for you. It generates two classes per Core Data entity. The first class is for machine consumption and is continuously overwritten as the model changes. The second class is for all your custom logic and is never overwritten.

Mogenerator has a list of other benefits which include the following:

  • No need to use NSNumber objects when reading or writing numeric attributes.
  • Helper methods for working with sets.
  • Helper methods for creating new entities
  • A method for entity identification.

Mogenerator can be installed from the DMG available on the Mogenerator website, or alternatively through Homebrew. To install Mogenerator using Homebrew, open Terminal and run the following command:

brew install mogenerator

Once installed, use the cd command to change to your app’s directory, then run Mogenerator from Terminal like so:

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

In the command above, you call Mogenerator followed by the location of your model with the -m option. You can also specify where the generated classes should be located with the -O option. When working with ARC you should also pass the --template-var arc=true option.

You can make Xcode run Mogenerator for you by creating a Run Script Build Phase. Build Phases are descriptions of tasks that need to be performed by Xcode during a build.

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

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

if [ "${CONFIGURATION}" == "Debug" ]; thenecho "Running Mogenerator"mogenerator -m MySampleApp/ExampleModel.xcdatamodeld -O MySampleApp/Model --template-var arc=trueecho "Finished Mogenerator"elseecho "Skipping Mogenerator"fi

The above run script will cause Xcode to run Mogenerator every time you run a debug build command. If there are no changes to the model, Mogenerator will do nothing and exit.

Now that you have incorporated Mogenerator into your workflow for quick subclass generation, you should take advantage of its other features.

For example, instead of unwrapping primitive values every time you can just add the suffix Value on to them as illustrated by the following code snippet:

// Without Mogeneratorif ([person.isFriend boolValue]) {  // Do some work} // With Mogeneratorif (person.isFriendValue) {  // Do some work}

Since bool types are stored as NSNumber in Core Data, you must call boolValue on the person object before checking if the value is true. With Mogenerator, that extra step is no longer required as you can simply call isFriendValue.

If Mogenerator looks like a useful addition to your toolbox, you can find more information on Mogenerator at its Github repository.

1. Instruments

Instruments is the tool of choice for investigating almost all performance and memory issues on OS X and iOS — including Core Data issues. The other tools in this list offer a lot of automation and convenience, but Instruments will typically be your first stop when investigating any issues or doing any performance tuning.

The Time Profiler and Core Data templates, shown below, are the most useful for Core Data profiling:

Screenshot 2014-07-22 11.16.37

The default Core Data template, with the optional Faults Instrument feature added in, provides the following features to help you tune and monitor your app’s performance:

  • Core Data Fetches Instrument — Captures fetch count and duration of fetch operations.
  • Core Data Cache Misses Instrument — Captures information about fault events that result in cache misses.
  • Core Data Saves Instrument — Captures information on managed object context save events.
  • Core Data Faults Instrument — Captures information on fault events that occur during lazy initialization of NSManagedObjects or relationships.

Here is a typical instruments profile from a Core Data app. You can see when fetch requests are occurring and how long they take, when and how often save operations happen and whenever faults are being fired:

Screenshot 2014-07-22 11.22.07

For more information about Instruments, check out our tutorial on How to Use Instruments in Xcode.

Where To Go From Here?

Core Data is a powerful framework, but it comes with a lot of development overhead. However, the tools and libraries in this article give you some methods to help you efficiently and effectively tackle that overhead.

Again, if you want to learn about how to use Core Data with Swift, check out our upcoming book Core Data by Tutorials, which is fully updated for iOS 8 and Swift.

In the meantime, if you have any other Core Data tools or libraries you really would like to recommend to others, please join the forum discussion below!

0 0
原创粉丝点击