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 126 0
Tài liệu đã được kiểm tra trùng lặp

Đ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

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 synchron

Trang 1

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];

Trang 2

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];

Trang 3

[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

Trang 4

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

Trang 5

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]

Trang 6

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

Trang 7

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;

Trang 8

@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

Trang 9

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

Trang 10

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

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

TỪ KHÓA LIÊN QUAN