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!
The code example below shows you how to set up RestKit to access the OpenWeatherMap API and map the JSON response of the
into a
Here’s what’s going on in the code above:
First, you create a
Here,
Finally, you execute the operation with the requisite success and failure blocks. When RestKit sees a response coming back that matches the defined
In the code above there’s no need for manual JSON parsing, checking for
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.
service requests as simple as possible as it creates, fetches, and populates
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:
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
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
server classes.
For complete information on setting up and using MMRecord, the readme on the MMRecord Github repository is the best place to
start.
Here’s a view of MagicalRecord in operations:
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:
You call
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.
The open source project
queue.
Below is an example of accessing an instance of
In the code above you’re trying to read
If you were to run the above example using
Note: You should remove
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
Xcodeʼs Scheme Editor.
However, until you can phase out support for earlier OS versions in your app,
The GDCoreDataConcurrencyDebugging README on Github is your best resource
information on installing and using this tool.
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
CoreData-hs would create the following category for you:
As you can see there are a lot of methods generated! As an example, here’s the implementation generated for
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
you can call
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.
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
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
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
file using the SQLite3 program as follows (the example app here is called AddressBook, your filename will be different):
You’ll see the following prompt appear in the console:
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 responds to your query with a textual listing of the tables in the schema as follows:
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:
SQLite would respond with the count of each distinct city in your address book database, as shown in the example below:
To exit the SQLite3 terminal program, simply execute the following command:
For more information on SQLite3, view its
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
When instantiating the
A
The second method handles deletions:
It’s far easier to implement the two required methods of
You can find out more about MDMCoreData at the MDMCoreData Github repository.
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
The
Similarly, to set the first name of a
A better approach is to use standard accessor methods or dot syntax; however, to do this you must implement a custom subclass of
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
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:
Once installed, use the
In the command above, you call Mogenerator followed by the location of your model with the
When working with ARC you should also pass the
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
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
Since bool types are stored as
step is no longer required as you can simply call
If Mogenerator looks like a useful addition to your toolbox, you can find more information on Mogenerator at its Github repository.
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
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.
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!
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
/weatherendpoint
into a
WFWeathermanaged 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]; } |
First, you create a
RKEntityMappingobject that tells RestKit how to map API responses to attributes of
WFWeather.
Here,
RKResponseDescriptorties responses from
/data/2.5/weatherto the
RKEntityMappinginstance above.
RKManagedObjectRequestOperationdefines 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
RKResponseDescriptornoted above.
Finally, you execute the operation with the requisite success and failure blocks. When RestKit sees a response coming back that matches the defined
RKResponseDescriptorit 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 webservice 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]); }]; |
How does MMRecord know how to locate your objects in the API response? Your managed objects must be subclasses of
MMRecordand override
keyPathForResponseObjectas 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 |
keyPathForResponseObjectreturns 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/weathercall.
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]; |
follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 1 [MagicalRecord setupCoreDataStackWithStoreNamed:@"ExampleDatabase.sqlite"]; return YES; } |
setupCoreDataStackWithStoreNamedin
application:didFinishLaunchingWithOptions:with the name of your SQLite file. This sets up your instances of
NSPersistentStoreCoordinator,
NSManagedObjectModeland
NSManagedObjectContextso
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. TheperformBlockAPIs help, but it’s still easy to make mistakes.
The open source project
GDCoreDataConcurrencyDebuggingcan 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
NSManagedObjectfrom 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; }]; |
namein
context2from an object that was originally created in
context1.
If you were to run the above example using
GDCoreDataConcurrencyDebuggingyou’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 |
GDCoreDataConcurrencyDebuggingfrom 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 1to your app on launch via
Xcodeʼs Scheme Editor.
However, until you can phase out support for earlier OS versions in your app,
GDCoreDataConcurrencyDebuggingwill 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 DataModel. 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
WFForecastentity with a
timeStamp,
temp, and
summaryattribute,
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 |
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; } |
WFForecastobjects 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]); }]; |
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 andvisualize 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 |
SQLite version 3.7.13 2012-07-17 17:46:21 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> |
For example, to view the schema Core Data is using, execute the following command:
sqlite> select * from sqlite_master; |
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> |
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 |
San Diego|23 Orlando|34 Houston|21 |
sqlite> .exit |
manpage 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 workingwith 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
UITableViewDataSourceand
NSFetchedResultsControllerDelegateprotocol, you can just set your table’s data source to an instance of
MDMFetchedResultsTableDataSource.
When instantiating the
MDMFetchedResultsTableDataSourceobject 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; } |
MDMFetchedResultsTableDataSourcedoes 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; } |
- (void)dataSource:(MDMFetchedResultsTableDataSource *)dataSource deleteObject:(id)object atIndexPath:(NSIndexPath *)indexPath { [self.persistenceController.managedObjectContext deleteObject:object]; } |
MDMFetchedResultsTableDataSourcethan 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 customNSManagedObjectclasses. 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
personCore Data entity, you could read and write attributes like this:
NSString *personName = [person valueForKey:@"firstName"]; [person setValue:@"Ned" forKey:@"firstName"]; |
personobject above is an instance of
NSManagedObjectwith an attribute named
firstName. To read
firstName, you use
valueForKey:with the key
firstName.
Similarly, to set the first name of a
personobject 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
NSManagedObjectfor 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
NSNumberobjects 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 |
cdcommand 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 |
-moption. You can also specify where the generated classes should be located with the
-Ooption.
When working with ARC you should also pass the
--template-var arc=trueoption.
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
mogeneratoras 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 |
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
Valueon 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 } |
NSNumberin Core Data, you must call
boolValueon the
personobject 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
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:
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!
相关文章推荐
- 关于iptables
- OpenGL-创建视口
- Linux GCC常用命令
- gnuradio+USRP实现OpenBTS 5.0安装
- linux pgrep
- linux grep
- 聊聊Linux中的线程本地存储(1)——什么是TLS
- Php学习-配置LAMP(Ubuntu16.04桌面版+Apache2+Mysql5.6+Php7)
- OPenfire源码环境配置和编译
- linux文件系统(三) - 内核回写机制
- Hadoop 基础知识---之HDFS篇
- linux几种时间函数总结
- Linux内核调试技术——进程D状态死锁检测
- Bash的循环与分支
- -Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable a
- elk平台分析nginx日志的基本搭建
- C语言及shell描述符重定向
- Tomcat的管道和阀
- 手把手教你阿里云linux上安装jdk
- CALayer Animatable Properties