您的位置:首页 > 运维架构

Top 10 Core Data Tools and Libraries

2016-07-17 15:30 483 查看
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:
First, you create a 
RKEntityMapping
 object that tells RestKit how to map API responses to attributes of 
WFWeather
.
Here, 
RKResponseDescriptor
 ties responses from 
/data/2.5/weather
 to the 
RKEntityMapping
 instance above.
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.
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 
NSManagedObject
s 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 
main
for 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:

// Fetching
NSArray *people = [Person MR_findAll];
 
// Creating
Person *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 
NSManagedObject
s 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:



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:



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: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
.

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" ]; then
echo "Running Mogenerator"
mogenerator -m MySampleApp/ExampleModel.xcdatamodeld -O MySampleApp/Model --template-var arc=true
echo "Finished Mogenerator"
else
echo "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 Mogenerator
if ([person.isFriend boolValue]) {
// Do some work
}
 
// With Mogenerator
if (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:



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 
NSManagedObject
s 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:



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!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: