1. Trang chủ
  2. » Công Nghệ Thông Tin

Phát triển ứng dụng cho iPhone và iPad - part 29 doc

10 123 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 3,53 MB

Nội dung

In order to implement a subclass of NSOperation , at a minimum, you need to implement a custom initializer method that you use to confi gure your object for use and a main method that will perform the task. You can also implement any other custom methods that you need such as accessor methods to get at the operation ’ s data or dealloc to free memory allocated by the operation. Once you have created your operation classes, you need to tell the system to execute them. You accomplish this by creating an instance of NSOperationQueue and adding your NSOperation subclasses to the queue. The system uses operation queues to schedule and execute your operations. The system manages the queues, which handle the details of scheduling and executing your threads for you. It is possible to confi gure dependencies between operations when adding operations to an operation queue. You can also prioritize operations when you add them to a queue. Core Data Threading Example As I mentioned, this example application will generate fi ve random numbers and pause for 1 second between each number to simulate a time consuming synchronous operation. This code will block the main thread causing the user interface to become unresponsive while you are generating the random numbers. Then, you will take the random number generation and move it on to its own thread. Once you do this, you will see that the user interface remains responsive while you generate the random numbers. In this example, you will be inserting data into the Core Data context on an off thread. This is generally not recommended; however I am doing it in this example to demonstrate the mechanics of threading with Core Data. In production applications, you should generally only use threads to perform time consuming reads and queries from Core Data and not to perform inserts or updates. To get started, open Xcode and start a new Navigation - based application. Make sure that “ Use Core Data for storage ” is checked. Call your new application RandomNumbers. The fi rst thing that you will do is update the default data model to hold your random numbers. Open the data model fi le RandomNumbers.xcdatamodel . In the Event entity, change the name of the timeStamp attribute to randomNumber . Change the type of the randomNumber attribute to Integer16 . Next, you need to make some changes to the RootViewController implementation fi le. In the configureCell:atIndexPath: method, change the reference to the old timeStamp attribute to randomNumber . The configureCell method should look like this: - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath]; Safely Threading with Core Data ❘ 249 CH009.indd 249CH009.indd 249 9/18/10 9:57:49 AM9/18/10 9:57:49 AM 250 ❘ CHAPTER 9 CORE DATA MIGRATION AND PERFORMANCE cell.textLabel.text = [[managedObject valueForKey:@”randomNumber”] description]; } RootViewController.m You will also need to make this change in the fetchedResultsController accessor method: - (NSFetchedResultsController *)fetchedResultsController { if (fetchedResultsController != nil) { return fetchedResultsController; } /* Set up the fetched results controller. */ // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@”Event” inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; // Set the batch size to a suitable number. [fetchRequest setFetchBatchSize:20]; // Edit the sort key as appropriate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@”randomNumber” ascending:NO]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means “no sections”. NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@”Root”]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; [aFetchedResultsController release]; [fetchRequest release]; CH009.indd 250CH009.indd 250 9/18/10 9:58:00 AM9/18/10 9:58:00 AM [sortDescriptor release]; [sortDescriptors release]; return fetchedResultsController; } RootViewController.m Blocking the Main Thread Now you are ready to generate your random numbers and populate the Core Data database. You will implement the insertNewObject method, which runs when the user taps the plus button in the top - right corner of the application. Delete the existing implementation and add the following: - (void)insertNewObject { // Create a new instance of the entity managed by the fetched results // controller. NSManagedObjectContext *context = [fetchedResultsController managedObjectContext]; NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity]; // Generate 5 random numbers waiting 1 second between them. for (int i=0; i < 5; i++){ NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; [newManagedObject setValue:[NSNumber numberWithInt:1 + arc4random() % 10000] forKey:@”randomNumber”]; // Simulate long synchronous blocking call sleep(1); } // Save the context. NSError *error = nil; if (![context save: & error]) { NSLog(@”Unresolved error %@, %@”, error, [error userInfo]); abort(); } } RootViewController.m Safely Threading with Core Data ❘ 251 CH009.indd 251CH009.indd 251 9/18/10 9:58:01 AM9/18/10 9:58:01 AM 252 ❘ CHAPTER 9 CORE DATA MIGRATION AND PERFORMANCE The method fi rst obtains a reference to the context, and then creates an entity description based on the entity that the fetched results controller manages. In the for loop, you create a new managed object to hold your random number. Then, you generate a random number and assign it to the randomNumber attribute of your new managed object. Then you call the sleep function to sleep the thread for 1 second. Sleep is a blocking call, so you use it here to simulate some code that takes a signifi cant amount of time to execute. Finally, you save the context. Build and run the application. When the application starts, tap the plus sign in the top - right corner of the interface to invoke the insertNewObject method and generate some random numbers. Try to scroll the TableView while you generate the random numbers. Notice how the user interface becomes unresponsive while you are generating the random numbers. The synchronous call to sleep is blocking the main thread, which controls the UI. In about 5 seconds, you will see the random numbers appear in the interface and control will return to the user. Moving the Blocking Call Clearly, this is not a good situation for your application. Your users will be confused when the application doesn ’ t respond when they try to scroll the list while the application is generating random numbers. You solve this problem by creating an NSOperation subclass and then moving the code to generate the random numbers onto a new thread. Create a new Objective - C class that is a subclass of NSObject and call it RandomOperation . In the header fi le RandomOperation.h , change the base class from NSObject to NSOperation . Remember that Core Data is inherently not thread - safe. When you use Core Data with threads, the key is to create a separate context on each thread. You create a context against a Persistent Store Coordinator. You need to write an initializer for your operation that accepts a Persistent Store Coordinator. You then use that coordinator to create a new context into which you will add your random numbers. In the header fi le, add an instance variable and property for the coordinator. You should note that you do not need to retain the coordinator because you will only hold a pointer to the shared coordinator. Finally, add an init method called initWithPersistentStoreCoordinator that accepts NSPersistentStoreCoordinator as an input parameter. Here is the RandomOperation header: #import < Foundation/Foundation.h > @interface RandomOperation: NSOperation { NSPersistentStoreCoordinator *coordinator; } -(id) initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator*)coord; @property (nonatomic,assign) NSPersistentStoreCoordinator *coordinator; @end RandomOperation.h CH009.indd 252CH009.indd 252 9/18/10 9:58:01 AM9/18/10 9:58:01 AM Let ’ s move on to the RandomOperation implementation fi le. In the implementation, synthesize the property and implement the initWithPersistentStoreCoordinator method like this: @synthesize coordinator; -(id) initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator*)coord { if (self == [super init]) { self.coordinator = coord; } return self; } RandomOperation.m All you are doing in this method is calling the superclass init method and then storing the NSPersistentStoreCoordinator input parameter in the class coordinator property. Next, implement the dealloc method to call dealloc on the superclass: -(void) dealloc { [super dealloc]; } RandomOperation.m Now, you need to implement the main method to actually do the work: -(void) main { // Create a context using the persistent store coordinator NSManagedObjectContext *managedObjectContext; if (self.coordinator != nil) { managedObjectContext = [[NSManagedObjectContext alloc] init]; [managedObjectContext setPersistentStoreCoordinator: self.coordinator]; } else { return; } // Generate 5 random numbers waiting 1 second between them. for (int i=0; i < 5; i++){ NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:@”Event” inManagedObjectContext:managedObjectContext]; [newManagedObject setValue:[NSNumber numberWithInt:1 + arc4random() % 10000] Safely Threading with Core Data ❘ 253 CH009.indd 253CH009.indd 253 9/18/10 9:58:02 AM9/18/10 9:58:02 AM 254 ❘ CHAPTER 9 CORE DATA MIGRATION AND PERFORMANCE forKey:@”randomNumber”]; // Simulate long synchronous blocking call sleep(1); } // Save the context. NSError *error = nil; if (![managedObjectContext save: & error]) { NSLog(@”Unresolved error %@, %@”, error, [error userInfo]); abort(); } // Clean up [managedObjectContext release]; } RandomOperation.m Most of this method is the same as the insertNewObject method from the RootViewController . The major difference is that in the fi rst part of the method, you use the class pointer to the Persistent Store Coordinator to create a new managed object context. There is some added safety code here to return from the method if you do not have a pointer to a valid coordinator. You then proceed to create Managed Objects containing the random numbers just as you did in insertNewObject . Finally, you call save on the context and release the local context. You are fi nished with the RandomOperation class. Back in the RootViewController , you need to change the insertNewObject method to use your operation. At the top of the implementation fi le, add #include for the RandomOperation header and another #include to include the app delegate: #import “RandomOperation.h” #import “RandomNumbersAppDelegate.h” RootViewController.m You will get a reference to the coordinator from the app delegate and pass it into your RandomOperation in the initializer. Delete all of the existing code from the insertNewObject method because you have moved this code into your RandomOperation class. You do, however, need to implement the insertNewObject method to use your RandomOperation class. The following is the implementation of insertNewObject : - (void)insertNewObject { // Create an instance of NSOperationQueue NSOperationQueue* operationQueue = [[NSOperationQueue alloc] init]; // Get a reference to the app delegate to get the coordinator CH009.indd 254CH009.indd 254 9/18/10 9:58:03 AM9/18/10 9:58:03 AM RandomNumbersAppDelegate* appDelegate = [UIApplication sharedApplication].delegate; // Create an instance of the operation RandomOperation* ourOperation = [[RandomOperation alloc] initWithPersistentStoreCoordinator: appDelegate.persistentStoreCoordinator]; // Add the operation to the operation queue [operationQueue addOperation:ourOperation]; // Clean up [ourOperation release]; [operationQueue release]; } RootViewController.m First, you create an instance of the NSOperationQueue that you will use to hold and execute your operation. Next, you get a reference to the App Delegate that you will use to get a reference to the Persistent Store Coordinator. Then, you create an instance of your RandomOperation class and pass it a reference to the Persistent Store Coordinator. Finally, you call the addOperation method on the operation queue to add your operation to the queue and instruct the queue to execute your operation. Last, you release the variables that you allocated in the method. Before you run the application, you need to modify the App Delegate to expose the Persistent Store Coordinator property. In the RandomNumbersAppDelegate , move the interface declaration for the Core Data Stack properties out of the implementation fi le and into the header. Remove the @interface and @end beginning and ending tags. You need the coordinator to be public so that you can retrieve it when you go to create your operation. The app delegate header fi le should look like this: #import < UIKit/UIKit.h > #import < CoreData/CoreData.h > @interface RandomNumbersAppDelegate : NSObject < UIApplicationDelegate > { NSManagedObjectModel *managedObjectModel; NSManagedObjectContext *managedObjectContext; NSPersistentStoreCoordinator *persistentStoreCoordinator; UIWindow *window; UINavigationController *navigationController; } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) IBOutlet UINavigationController *navigationController; @property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; Safely Threading with Core Data ❘ 255 CH009.indd 255CH009.indd 255 9/18/10 9:58:03 AM9/18/10 9:58:03 AM 256 ❘ CHAPTER 9 CORE DATA MIGRATION AND PERFORMANCE @property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext; @property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (NSString *)applicationDocumentsDirectory; @end RandomNumbersAppDelegate.h Delete your old version of the application from the simulator to delete the old Core Data database. Now run the program and click the plus button to add some new random numbers. You will notice that the interface is now responsive while you are generating the random numbers. However, there is one problem: the numbers never appear. If you wait a few seconds, quit the application, and then restart it, you will see fi ve new numbers in the TableView. The problem is that the context in the RootViewController is unaware that you have added records to the Core Data database on another thread, so the FetchedResultsController has not updated the TableView. Fortunately, there is a way to handle this. Whenever a context performs a save, the context sends out an NSManagedObjectContextDidSaveNotification notifi cation. The NSManagedObjectContext class has a method - (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification that accepts a notifi cation and merges the changes contained in the notifi cation into the context. Therefore, you need to write some code to listen for and handle the notifi cation. When you receive the notifi cation, you need to call mergeChangesFromContextDidSaveNotification : to merge the changes into the context on the main thread. In the viewDidLoad method in the RootViewController , you need to get a reference to the default notifi cation center and add self as an observer for the NSManagedObjectContextDidSaveNotification message. You do this at the end of viewDidLoad . The following is the revised viewDidLoad method: - (void)viewDidLoad { [super viewDidLoad]; // Set up the edit and add buttons. self.navigationItem.leftBarButtonItem = self.editButtonItem; UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject)]; self.navigationItem.rightBarButtonItem = addButton; [addButton release]; NSError *error = nil; if (![[self fetchedResultsController] performFetch: & error]) { NSLog(@”Unresolved error %@, %@”, error, [error userInfo]); abort(); } // Add an observer for the NSManagedObjectContextDidSaveNotification message CH009.indd 256CH009.indd 256 9/18/10 9:58:04 AM9/18/10 9:58:04 AM NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(contextSaved:) name:NSManagedObjectContextDidSaveNotification object:nil ]; } RootViewController.m Any time that the save method is called on any context, Core Data generates an NSManagedObjectContextDidSaveNotification message. You are now listening for this message, and when you receive it, you will execute the contextSaved method in the RootViewController . Now, all you have to do is implement the contextSaved method to take the notifi cation and call mergeChangesFromContextDidSaveNotification : -(void) contextSaved:(NSNotification*)notification { [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; } RootViewController.m The last thing that you need to do is implement dealloc to remove self as an observer on the notifi cation center: - (void)dealloc { [fetchedResultsController release]; [managedObjectContext release]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } RootViewController.m Delete your old version of the application from the simulator to delete the old Core Data database. Build and run the application. Click the plus button. Notice how the UI is still responsive. In a few moments, the save occurs, the notifi cation is sent, contextSaved is called, the changes are merged into the context, and the FetchedResultsController updates the table with the new random numbers. You have successfully moved a time - consuming blocking call off the main thread and on to another thread. CORE DATA PERFORMANCE When designing and building an application, one of your primary concerns should be performance. In the context of data - driven applications, let ’ s look at a few specifi c aspects of overall application performance. Core Data Performance ❘ 257 CH009.indd 257CH009.indd 257 9/18/10 9:58:04 AM9/18/10 9:58:04 AM 258 ❘ CHAPTER 9 CORE DATA MIGRATION AND PERFORMANCE The fi rst aspect to consider is perceived performance . Perceived performance can be defi ned as how quickly your application appears to complete a task. The key word in this defi nition is appears . The threading program that you built in the last section is a perfect example of perceived performance. In the fi rst phase of the example, when your user interface was frozen, you probably felt that the performance of the application was poor because it locked up for 5 seconds as it generated the random numbers. After you moved the random number generation to another thread, the application probably felt much snappier, even though the length of time from when you tapped the plus button to the time when the random numbers appeared in the TableView was exactly the same. The difference was that the interface was responsive so you perceived the application to be working more quickly. You have already learned how to use threading as a tool for increasing the responsiveness and therefore the perceived performance of your application. In this section, I will focus on another aspect of iOS programming that has a great impact on performance — memory management. On embedded devices such as the iPhone and iPad, you need to be particularly concerned about memory as the iOS does not currently support virtual memory or paging. If your application runs low on memory, the OS will tell certain parts of your application to unload. This will cause a time - consuming re - initialization to occur when the user needs to use these parts of your application again. Additionally, it is possible that your application can run out of memory, causing the OS to terminate your application. After you look at some memory management items, you will look at a few ways that you can increase the actual speed of your applications. Faulting Core Data uses a technique called faulting to reduce the memory footprint of your application by keeping unused data out of memory. Faulted objects are instances of NSManagedObject or your NSManagedObject subclass that are retrieved in a fetch request, but not all of the properties of the object are immediately loaded into memory. Core Data can conserve memory by loading only the parts of an object or a relationship that you need. Core Data does not populate a faulted object ’ s properties until you access them. This process, called fault realization , happens automatically. You do not have to execute a fetch to realize a fault, but behind the scenes, Core Data may need to execute additional fetches to realize faults. Faulting most often occurs when objects are associated with relationships. If you recall the sample from Chapter 6, “Modeling Data in Xcode,” you had two entities, Task and Location, as illustrated in Figure 9 - 7. On the All Tasks screen, you queried Core Data for a list of all Tasks. Because the Task entity has a relationship entity, you would think that Core Data would load the related Location object into memory as well. However, it does not. The Location entity in this instance is faulted. Core Data has no reason to load a related Location object into memory unless the code asks for it. FIGURE 9 - 7: Tasks application data model CH009.indd 258CH009.indd 258 9/18/10 9:58:05 AM9/18/10 9:58:05 AM . memory, the OS will tell certain parts of your application to unload. This will cause a time - consuming re - initialization to occur when the user needs to use these parts of your application again great impact on performance — memory management. On embedded devices such as the iPhone and iPad, you need to be particularly concerned about memory as the iOS does not currently support virtual. (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (NSString *)applicationDocumentsDirectory; @end RandomNumbersAppDelegate.h Delete your old version of

Ngày đăng: 04/07/2014, 21:20

TỪ KHÓA LIÊN QUAN