Core Data does not bring relationships to a faulted object into memory until you access them.. This could be useful in situations where you want to use Core Data to manage your applicati
Trang 1Core Data does not bring relationships to a faulted object into memory until you access them This behavior allows Core Data to conserve memory by keeping unused objects out of memory until you need them
In this example, Core Data is preventing only one object, the Location, from being loaded You may be thinking that this doesn ’ t save too much memory That is true, but imagine if the location object were also related to a series of other objects, each with its own relations, and so on In large object hierarchies, the memory conserved by faulting can be considerable
Data Store Types
When using Core Data, you can specify the type of data store that Core Data uses to persist your data Your options are SQLite, binary, and in - memory Although you will usually use the SQLite store type, you will take a brief look at all of the types
The in - memory store does not actually persist your data at all As the name says, the data store
is in memory When your application quits, your data is gone This could be useful in situations where you want to use Core Data to manage your application data when that data does not need
to be persisted from session to session This is typically not very useful in iOS applications as your application session could end at any time, particularly if the user of an iPhone gets a call
The binary store type uses a proprietary binary format to store your data The primary drawback
of using the binary format is that all of the data stored in the data store is loaded into memory when the data store is loaded There is no provision to leave parts of the data on disk In contrast,
an SQLite database remains largely left on disk and Core Data only brings the parts that you specifi cally query for into memory
Although there is no “ disk ” on the iPhone or iPad, there is a difference between application memory and storage memory When I say “ on disk, ” I am referring to the space on the device that
is used for data storage The iPhone or iPad cannot use this storage space to execute applications,
so there is a distinction between the application memory available for use by your application ’ s execution and disk space, which is available for data storage
Storing Binary Data
You may be tempted to store binary data such as images or sound clips in your Managed Objects using Core Data In general, this is not a good idea You are generally better off storing the binary data on disk and storing the path to the data in Core Data as you did in the catalog example earlier
in the book because you may store a large chunk of data in an object and not realize that you are bringing all of that data into memory when Core Data loads the object For example, if you stored the images for each of your catalog items in the database, but did not display them on the main catalog screen, these potentially large images would still be loaded into memory
If you do store binary data in Core Data, do not store the data in frequently accessed rows For example, you should not store an image in the same managed object that you use to display items
in a table if you are not also displaying the image because the image will be loaded into memory regardless of whether you display the image or not
Trang 2Entity Inheritance
You may remember from Chapter 6 that you
can implement an entity hierarchy in Core
Data using inheritance and the parent fi eld
of an entity in the entity detail pane I have
illustrated a hypothetical entity hierarchy in
Figure 9 - 8
Although this is a feature of Core Data, you
need to be aware of how the implementation
uses memory in the back - end SQLite data
store An entity hierarchy is not the same as the equivalent object hierarchy in an object - oriented
design In the entity hierarchy, all of the attributes of all of the entities in the hierarchy are stored
in the same database table This can lead to ineffi ciency in how the data is stored on disk, causing
excessive memory usage
To illustrate this, imagine that in the hierarchy
illustrated in Figure 9 - 8, the Product entity has
attributes P1 and P2, the Hammer has attributes
H1 and H2, and the Screw has attributes S1 and
S2 In the current implementation of Core Data,
Core Data stores the data for all three of these
entities in a single table, as illustrated in Figure 9 - 9
You can see that the Hammer entities have unused
space in the table for the Screw - related fi elds and
vice versa for Screw objects Although this is a
simple example, it illustrates the storage issue The
problem gets worse as your inheritance hierarchy
gets larger and deeper
Runtime Performance
This chapter covered improving perceived performance with threading and being conscious of how
Core Data is using memory In this section, I will just lay out a few general tips that you can look to
in an attempt to increase the runtime performance of your application
First, when designing your model, avoid over - normalization of your data Remember that you are
not designing a relational database but an object persistence mechanism Feel free to de - normalize
your data if it makes building the displays for the application easier You should try to fi nd a balance
between normalization and speeding up your queries for rapid display Accessing data through a
relationship is more expensive than retrieving an attribute Consider this before normalizing your
data In addition, querying across relationships is expensive Determine if you really need to do it,
or de - normalize the data if it makes sense For example, if you have an application that stores names
and addresses, it may make sense from a UI perspective to keep the state name in the same table as
the rest of the address as opposed to normalizing it out into its own table You may not want to
FIGURE 9 - 8: Entity inheritance hierarchy
FIGURE 9 - 9: Core Data storage for entity inheritance
Trang 3take the overhead penalty for following a relationship to a state table every time that you need to look up an address if you will always be showing the address and state together
I know that it may seem obvious, but you should try to fetch only the data that you need when you need it This tip ties in with the “ Faulting ” section from earlier in the chapter Core Data will generally not bring in data that you are not going to use Therefore, you should be careful to avoid building your queries in a way that forces Core Data to bring data into memory that you may not need
Another thing that you can do to increase your application performance is take advantage of Core Data caching The Persistent Store Coordinator holds fetched results in its caches This is particularly useful when you can set up a background thread to fetch data while the foreground remains responsive Then, the foreground thread can read the data from the persistent coordinator ’ s cache when necessary, avoiding another trip to the data store
The fi nal tip has to do with the order of the items in your search predicate Search order in predicates is important Put likely - to - fail criteria fi rst so the comparison can end quickly The engine evaluates predicates in order, and if one part of the predicate fails, the engine will move on
to the next record You can potentially reduce query times by placing likely - to - fail criteria at the beginning of a predicate if it makes sense
Managing Changes with the Fetched Results Controller
In this section, you will look at how you can use NSFetchedResultsController to update your TableView based on changes to a result set
When the data managed by NSFetchedResultsController changes, the fetched results controller calls several delegate methods You have the option to either use these delegate methods to update the associated TableView based on each individual change, or simply handle one delegate method and tell the TableView to reload its data
Implementing the delegate methods to handle individual updates can yield better performance because you are not reloading all of the table data each time there is an update There is a tradeoff, however, because if there are many simultaneous changes to the data, it may be cheaper to just reload all of the data because presenting many individual changes can be time consuming
To keep the Tasks application simple, I took the latter approach In the RootViewController , you implemented the controllerDidChangeContent delegate method to reload the data for the TableView Here is the implementation:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.taskTableView reloadData];
}
RootViewController.m
Compare how you just reloaded all of the data for the table in the Tasks application to how you handle individual cell updates in the random numbers application In the random numbers project, you handled each update individually by accepting the template code for the RootViewControllers The delegate methods that the NSFetchedResultsController calls
Trang 4are controllerWillChangeContent :, controller:didChangeSection:atIndex:forChangeType ,
controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: , and
controllerDidChangeContent :
The FetchedResultsController calls the controllerWillChangeContent: and
controllerDidChangeContent : methods to bracket the changes that are being made to the result
set You implement these methods to tell the table that changes will begin and end respectively
You can think of these methods as beginning and committing a transaction to the table Here is
the implementation in the RandomNumbers example:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
RootViewController.m
This code simply tells the TableView that updates are coming and that updates are fi nished The
endUpdates call triggers the TableView to display and optionally animate the changes to the data
The FetchedResultsController calls the controller:didChangeSection:atIndex:
forChangeType method when the sections of the TableView should change The two types
of changes that you will receive in this method are NSFetchedResultsChangeInsert and
NSFetchedResultsChangeDelete Your code will be able to determine if the changes to the data
have added or removed a section from the TableView Typically, you will implement a switch
statement to handle these two cases, as shown in the RandomNumbers sample:
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id < NSFetchedResultsSectionInfo > )sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:
[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:
[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
RootViewController.m
Trang 5This code simply inserts or deletes the section that it receives in the method call
Finally, the FetchedResultsController calls the controller:didChangeObject:atIndexPath:
forChangeType:newIndexPath: method when objects in the data store change In this method, your code needs to handle these four operations: NSFetchedResultsChangeInsert , NSFetchedResultsChangeDelete , NSFetchedResultsChangeMove , and
NSFetchedResultsChangeUpdate Again, you will usually handle calls to this method in
a switch statement and make the appropriate changes to your TableView as you did in the RandomNumbers example:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:
[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:
[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
} }
RootViewController.m
Trang 6PERFORMANCE ANALYSIS USING INSTRUMENTS
When confronted with a performance issue
in your application, the fi rst place that you
should turn is Instruments Instruments
is a GUI - based tool that you can use to
profi le your application in myriad ways
Instruments features a plug - in architecture
that allows you to pick and choose from
a library of recording instruments to use
in profi ling your application, as shown in
Figure 9 - 10 Additionally, you can create
custom instruments that use DTrace to
examine the execution of your application
Once you have selected the recording
instruments that you would like to use, you
can use Instruments to run your application,
either in the iPhone Simulator or on the
actual device, and gather the data that you
need to optimize your application After you start your application, Instruments will profi le the
application in the background as the application runs As your application is running, you will see
the graphs in Instruments react to operations that you perform in the application
The main feature of the tool ’ s interface is the timeline The premise is that you are recording the
operation of your application in real - time and can later go back and look at the state of your
application at any time during your test run This is an incredibly powerful tool to use when trying
to fi nd problems in your application
Once you narrow down a particular problem to a specifi c time on the timeline, you can drill down
into the application stack The stack provides information on the code that was executing at the
time that Instruments took the sample You can select your code segments in the stack to view the
source code
Typically, the biggest problems that you will fi nd in your application are issues with memory
management and leaks Instruments has a couple of tools, ObjectAlloc and Leaks, that can be
extremely helpful in detecting and crushing these often - illusive bugs You will take a closer look at
using these tools in Appendix A
Instruments is an incredibly powerful tool and I cannot recommend it enough If you have never
used Instruments, you owe it to yourself to take a closer look at the tool In this section, I walk
you through the use of Instruments to profi le your Core Data application using the Core Data –
related tools
Starting Instruments
In general, you can either start the Instruments tool by simply launching the application that is
located, by default, in /YourDriveName/Developer/Applications or by selecting Run ➪ Run
With Performance Tool from the menu bar in Xcode Unfortunately, Apple does not support the
FIGURE 9 - 10: Instruments tool library
Trang 7Core Data instruments for use with the iPhone or iPad However, there is a workaround that you can use because Core Data notifi cations are broadcast system - wide and the iPhone simulator is not sandboxed from the rest of the Mac OS X operating system
Even though Core Data is grayed out as an option in the “ Run with Performance Tool ” menu item, you can use the Core Data instruments with the Instruments tool You fi rst need to get Instruments
to recognize your application as an instrument target Open the RandomNumbers project in Xcode The easiest way to get your project into instruments is to use the Run with Performance Tool ➪ CPU sampler menu item in Xcode This will fi nd your project binary and start it in Instruments while running the CPU sampler
Because you want to use the Core Data tools and not the CPU sampler, you need to stop the Instruments session and add the Core Data tools to the instrumentation Press the red button to stop recording Now, Instruments knows where to fi nd the RandomNumbers application
Close the current window and discard the results of the CPU sampler run From the Instruments menu bar, select File ➪ New to create a new instrument session The new dialog displays a set of templates that you can use to create your Trace Document Select All under Mac OS X in the left pane and select Core Data in the right pane Finally, click Choose to create your new document
You should see a new Instruments window with three Core Data Instruments in the left - hand Instruments pane
You now have to tell Instruments which program you want to execute Open the Default Target pull down at the top of the window Select Launch Executable ➪ RandomNumbers as shown in Figure 9 - 11
FIGURE 9 - 11: Setting the Default Target
Trang 8You are now ready to begin your test run Click the red Record button in the top left of the
application to start your recording You will see the RandomNumbers application start up in the
simulator Once the application starts, you should see the timeline begin to move indicating that
Instruments is profi ling your application You should also notice a spike in the top instrument,
Core Data Fetches This indicates that a fetch has occurred in the application Tap the plus button
in the simulator to generate some random numbers In 5 seconds, you should see a spike in the
Core Data Saves instrument indicating that a save has occurred Stop the recording by clicking the
red button
The Instruments Interface
Now that you have fi nished your test run, you will want to analyze your data To do this, you
should understand how to use the Instruments interface You can see the interface as it should look
having completed your test run in Figure 9 - 12
Detail Pane
Extended Detail Pane
FIGURE 9 - 12: The Instruments interface
Trang 9The Instruments pane shows all of the instruments that are active for the current test run
The Track pane shows a graph representing different things for different instruments For the Core Data Fetches instrument, the Track pane shows the count of items fetched and the duration
of the fetch The Track pane displays the time that an event occurred during the test run You can adjust the time scale using the slider below the Instruments pane You can also scroll the Track pane using the scrollbar at the bottom of the pane
Below the Track pane is the Detail pane Like the Track pane, the Detail pane shows different details based on the tool that you have selected in the Instruments pane For the Core Data Fetches instrument, the Detail pane displays a sequence number, the caller of the fetch method, the fetched entity, the count of the number of items returned in the fetch, and the duration of the fetch in microseconds
You can select an item in the Detail pane to view more detail about the item in the Extended Detail pane The Extended Detail pane is particularly useful because it shows a stack trace for the method call that you have selected in the Detail pane Select a fetch in the Detail pane to view its stack trace
I fi nd it useful to display the fi le icons in the Extended Detail pane because doing this makes the calls in the stack that originated in your code obvious You can enable the fi le icons by clicking
on the gear icon in the Extended Details pane In Figure 9 - 12, you can see that the fetch selected in the Detail pane is initiated in the code in the RootViewController viewDidLoad method If you double - click on an item in the call stack that corresponds to one of your source code fi les, in this case the call to the RootViewController viewDidLoad method, Instruments will display the source code in the Detail pane, showing you the exact line of code that made the fetch request Click the Event List View button at the bottom left of the Detail pane to get back from the source code to the list of events
The Core Data Instruments
Four instruments are available for use with Core Data: Core Data Saves, Fetches, Faults, and Cache Misses
Core Data Saves reports the methods that invoke the Core Data save operation It also reports the duration of the operation in microseconds Opening the extended detail view shows the stack trace for the call and the time at which the call occurred
You can use this tool to discover how often you are saving data and fi nd a balance between saving too much and not saving enough Each save operation causes a disk write, which can hurt performance, but not saving often enough results in using excess memory to hold the data in memory
Core Data Fetches reports all fetches made against Core Data The tool reports the name of the fetched entity, the caller, the number of records returned, and the duration of the fetch in microseconds Opening the extended detail view shows the stack trace for the call and the time at which the call occurred
Fetch operations are costly as they read from disk You can use this tool to help optimize your search predicates to limit the number of rows returned by a fetch request
Trang 10Core Data Faults reports fault events that occur as the result of needing to realize object references
in to - many relationships The tool reports the method causing the fault, the object that was faulted,
the execution duration in microseconds, the relationship fault source, the name of the relationship,
and the relationship fault duration in microseconds You can open the extended detail view to see
the stack trace for the call and the time at which the call occurred
As discussed earlier in this chapter, faulting is a memory - saving technique that comes at the expense
of time when Core Data realizes the fault If you fi nd that Core Data is spending an excessive
amount of time resolving faults, but your memory consumption is low, you could consider pre
fetching related objects instead of having them realized through a fault
The Cache Misses tool reports fault events that result in cache misses This is a subset of the
information provided by the Faults instrument The tool displays the method that caused the cache
miss, the duration of execution for the fault handler in microseconds, the relationship cache miss
source, the name of the relationship, and the relationship cache miss duration Opening the extended
detail view shows the stack trace for the call and the time at which the call occurred
Similar to the remedy for extensive faulting, you can mitigate cache misses by pre - fetching data If
data is not in the cache, it leads to an expensive read from disk operation
MOVING FORWARD
In this chapter, you learned how to version your data models and migrate between versions to help
you add new features to your database and application I also covered how to safely use Core Data
in a threaded application with NSOperation to increase performance You looked at some Core
Data performance considerations and tips Finally, you learned how to use the Instruments tool to
observe your application ’ s performance when using Core Data
This ends the section of the book on Core Data You should now feel confi dent that you are able to
implement a data - driven application using this exciting and useful technology You should also have
all of the tools in your arsenal to be able to debug and troubleshoot problems that may arise while
you are using Core Data
In the next section of the book, you learn how to use XML and Web Services in your applications to
communicate with other applications over the Internet