Xem mẫu

Core Data Performance 259 Core 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 specifically 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. 260 CHAPTER 9 CORE DATA MIGRATION AND PERFORMANCE Entity 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 FIGURE 9- 8: Entity inheritance hierarchy 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 inefficiency 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 FIGURE 9- 9: Core Data storage for entity inheritance 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 find 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 Core Data Performance 261 take 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 final 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 first 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 262 CHAPTER 9 CORE DATA MIGRATION AND PERFORMANCE are 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 )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 Core Data Performance 263 This 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 ... - tailieumienphi.vn
nguon tai.lieu . vn