Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 68 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
68
Dung lượng
1,31 MB
Nội dung
Core Data Figure 19.2 Naming the data model Figure 19.3 Adding a new entity to the model 523 524 iPhone SDK Programming Figure 19.4 Configuring the new User entity Figure 19.5 Adding a new attribute to the User entity Name the relationship comments, pick the destination to be the Comment entity Choose a To-Many relationship (meaning the user will have many comments) and make the delete rule Cascade; this way, all comments that belong to a user will be deleted when that user is deleted See Figure 19.9 10 Select the Comment entity and add a relationship as shown in Figure 19.10 Notice that we choose the inverse to be comments and the delete rule to be No Action The resulting model diagram is shown in Figure 19.11 Core Data Figure 19.6 Specifying the parameters for the name attribute of the User entity Figure 19.7 The User entity with three attributes Figure 19.8 Adding a relationship in the User model 525 526 iPhone SDK Programming Figure 19.9 Figure 19.10 The comments relationship in the User model The user relationship in the Comment model Figure 19.11 The data model consisting of User and Comment entities with relationships Double arrows indicate To-Many relationship Core Data 527 19.4 Create, Read, Update and Delete (CRUD) In this section, we address the basic operations in persistence storage using Core Data 19.4.1 Create Creating a new managed object and saving it to the store is pretty straight forward You use NSEntityDescription’s class method insertNewObjectForEntityForName:inManagedObjectContext: to obtain a fresh managed object for a given entity in a given context After that, you simply assign values to its attributes and send a save: message to its context The following method creates a new record in the persistence store for a user with name, date of birth, and social security number -(BOOL)addUser:(NSString*) _userName dateOfBirth:(NSDate*)_dob andSocial:(NSString*)_social{ User *user = (User *)[NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.managedObjectContext]; user.dob = _dob; user.name = _userName; user.social = _social; return [self.managedObjectContext save:NULL]; } 19.4.2 Delete Deleting a record is simple Given a managed object, you send a deleteObject: message to the context with that object as an argument After that, you send the context a save: message to persist the change The following shows a code fragment that accomplishes that [self.managedObjectContext deleteObject:user]; [self.managedObjectContext save:NULL]; 19.4.3 Read and update To update a record, you retrieve it (Read), change its attributes and save its context Let’s turn our attention to retrieval in Core Data To retrieve managed objects from the persistence store, you execute fetch requests A fetch request is an instance of the NSFetchRequest class An instance of this class can be configured with three pieces of information: 528 iPhone SDK Programming • The name of the entity The fetch request must be associated with an entity To set the entity of a fetch request instance, you use the method setEntity: which takes an instance of NSEntityDescription class as an argument • The conditions of the search You specify the conditions using a predicate A predicate is an instance of the class NSPredicate This is optional • The sort criteria You specify the sort criteria by using the method setSortDescriptors: passing in an array of NSSortDescriptor instances Sort descriptors with lower indices in this array are given higher precedence in the sorting algorithm Sorting is optional Once you have configured the fetch request object, you use the following instance method of the NSManagedObjectContext class to retrieve the managed objects: - (NSArray *) executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error Unconditional fetch The following method shows how to retrieve all User’s managed objects: -(NSArray*)allUsers{ NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.managedObjectContext]; [request setEntity:entity]; return [self.managedObjectContext executeFetchRequest:request error:NULL]; } The method simply specifies the name of the entity whose managed objects are being retrieved and executes the fetch No sorting or conditions are specified (i.e., in the case of a SQL table, all rows are retrieved with no ordering) Conditional fetch As an example of specifying fetch conditions, the following method searches for all User records where the name attribute matches a query: -(NSArray*)usersWithNameQuery:(NSString*)_query{ NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" Core Data 529 inManagedObjectContext:self.managedObjectContext]; [request setEntity:entity]; NSPredicate *predicate = [NSPredicate predicateWithFormat: @"name like[cd] %@", [NSString stringWithFormat:@"*%@*", _query]]; [request setPredicate:predicate]; return [self.managedObjectContext executeFetchRequest:request error:NULL]; } To specify the condition, a new NSPredicate object is generated using the predicateWithFormat: class method Here, we are saying that the name attribute must contain the query string in it The [cd] means case- and diacritic-insensitive and the * is used to denote zero or more characters The predicateWithFormat: method does add quotes to the query After setting the predicate, the context is asked to execute the fetch Another example of using a predicate is retrieval of older users If you set the predicate to something like dob < some_date, you can retrieve all users older than that given date The following method does just that: -(NSArray*)olderUsers:(NSDate*)_date{ NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.managedObjectContext]; [request setEntity:entity]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"dob < %@", _date]; [request setPredicate:predicate]; return [self.managedObjectContext executeFetchRequest:request error:NULL]; } Sorted fetch To generate a sorted result set, you use NSSortDescriptor class You create a new NSSortDescriptor instance and initialize it with the initWithKey:ascending: initializer After that, you set the sort descriptors array of the fetch request object using the setSortDescriptors: method The following example shows how you can retrieve all users, sorted according to their date of birth (descending) -(NSArray*)allUsersSorted{ NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.managedObjectContext]; 530 iPhone SDK Programming [request setEntity:entity]; NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"dob" ascending:NO] autorelease]; [request setSortDescriptors: [NSArray arrayWithObject:sortDescriptor]]; return [self.managedObjectContext executeFetchRequest:request error:NULL]; } 19.5 Working with Relationships Relationships in Core Data are easy In the class representing a To-Many relationship, you declare a property of type reference to an NSSet class For example, in the User class, you can declare the relationship with Comment as follows: @property (retain) NSSet *comments; In the Comment class, you declare the Belongs-To relationship with User as follows: @property (retain) User *user; In an inverse relationship, you can change one side of the relationship, and the other side will change automatically For example, if you want to create a new comment for a given user, you can simply create the comment managed object, configure it with the text, latitude, longitude, and set its user property to the user managed object and save the context Now, the user’s comments property (represented by an instance of NSSet in the User class) has a new member The following method adds a new comment to an existing user: -(BOOL)addCommentByUser:(User*)user withText:(NSString*)text lat:(float)lat andLon:(float)lon{ Comment *comment = (Comment *)[NSEntityDescription insertNewObjectForEntityForName:@"Comment" inManagedObjectContext:self.managedObjectContext]; comment.user = user; comment.text = text; comment.lat = [NSNumber numberWithDouble:lat]; comment.lon = [NSNumber numberWithDouble:lon]; return [self.managedObjectContext save:NULL]; } Core Data 531 19.6 A Search Application In this section, we present a search application that uses a search display controller to search for stars of TV series The data store is an SQLite database managed by Core Data The records (instances of Star managed object class) are presented in a table view In order to use the Core Data framework, you need to add the CoreData.framework to your project as explained in Section D.4 In addition, you need to add the following import statement to your code: #import First, we discuss the UISearchDisplayController class and how it is used for managing a search bar and displaying the results of the search Next, we present the main pieces of the application 19.6.1 The UISearchDisplayController class The UISearchDisplayController class is used to manage a search bar as well as providing an overlay table view for search results When the user starts entering text in the search bar text field, the search display controller displays an overlay table view The table view, whose data source and delegate are configured using properties of the search display controller, is then populated with records corresponding to the search As the user changes the search text, the contents of the overlay table view is conditionally updated If the user taps on one of the search results rows, the delegate of the overlay table view gets notified which can result in, for example, the details of this record to be shown by pushing a new view controller Figure 19.12 shows the search display controller in action When the user taps the Cancel button, the overlay table view is removed from the display In addition, the navigation bar reappears and the search bar is brought down To use the search display view controller, you need to the following: Create and initialize it The controller is initialized by the following method: - (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController; The initializer takes as the first argument the search bar (an instance of UISearchBar class) The second argument should be the view controller that manages the display of the original content A search bar is a view that displays a text field, and cancel and bookmark buttons In addition, you can add different scope buttons beneath it Specify the data source for the search results You need to specify a value for the data source of the search results using the searchResultsDataSource property This property is declared as follows: 532 iPhone SDK Programming Figure 19.12 bar A search display controller overlaying a table view with results from a text query in a search @property(nonatomic,assign) id searchResultsDataSource; The object should adopt the UITableViewDataSource protocol and usually this object is the same as the view controller used in the initialization of the search display controller Specify the delegate of the search results The delegate of the overlay table view is specified using the searchResultsDelegate property The object should implement the UITableViewDelegate protocol As in the case of the data source, the view controller used to initialize the search display controller is usually used as the delegate of the search results table view Specify the search display view controller delegate You also need to specify a delegate for the search display view controller All methods are optional The following two delegate methods are used to specify if the search results in the overlay table view should be reloaded when the user changes the text in the search bar text field or changes the scope of the search: • searchDisplayController:shouldReloadTableForSearchString: 576 iPhone SDK Programming Figure D.7 Adding a user-defined setting for a specific configuration in a given target Add a user-defined setting as shown in Figure D.7 Figure D.8 shows the highlighted key/value pair to enter for the Debug configuration Figure D.8 The key/value pair for the Debug configuration Repeat this process and enter the key/value pair for the Release configuration as shown in Figure D.9 Figure D.9 The key/value pair for the Release configuration Now, when you change the configuration, the output of the main() function will change Using XCode 577 D.4 Using Frameworks The iPhone SDK comes with a variety of frameworks that you can use To use a framework, you need to #import its main header file and include a reference to that framework in the target When you create a new project, three frameworks are automatically added for you These three frameworks are: Foundation, UIKit, and CoreGraphics In addition, two import statements are added to the xxx_prefix.pch file, where xxx stands for the project’s name entered by you These two statements are: #import #import In this section, we show how you can add other frameworks to your target The Core Location framework is used here as an example First, you need to add a reference to the main header file by adding the following statement so that any code that references the Core Location classes is able to find them #import You can simply add this statement to the xxx_prefix.pch file This way, all other files can benefit from this import In addition, you need to add the framework to the target In XCode, select the Project->Edit Active Target menu as shown in Figure D.10 Figure D.10 Project > Edit Active Target menu 578 iPhone SDK Programming Figure D.11 Target Info window Figure D.12 The CoreLocation.framework library Using XCode 579 Figure D.13 The added Core Location library in the Linked Libraries section The Target Info window will appear as shown in Figure D.11 Click on the “+” button located at the bottom left-hand side A list of libraries will be shown Scroll down and locate the CoreLocation.framework as shown in Figure D.12 Select it and click “Add” Figure D.13 shows the added Core Location library in the Linked Libraries section E Unit Testing In this appendix, we show you how to add unit tests to your project By adding unit testing support, you’ll be able to write tests for your methods These tests will be added as a dependency on the building of your application This will result in the tests being run before actually building your application In the following, we walk you through a step-by-step process for adding unit testing We use a simple Employee class for demonstration purposes E.1 Adding a Unit Test Target In this section, we show you how to create a unit test target in your project Later, we will make our main target dependent on this unit test target, thus making sure that the unit tests are executed before building our target Right-click on Targets node in Groups & Files Select Add > New Target as shown in Figure E.1 Figure E.1 Selecting Add New Target from the context menu Select Cocoa under the Mac OS X category Scroll down and double-click on Unit Test Bundle as shown in Figure E.2 Enter the name of the target, such as MyUnitTestBundle, as shown in Figure E.3 582 iPhone SDK Programming Figure E.2 Choosing Unit Test Bundle for the new target Figure E.3 Naming the Unit Test Bundle E.2 Adapting to Foundation The target we just added in the previous section needs to be configured for use with the iPhone SDK instead of Mac OS X Unit Testing 583 Double-click on the unit test target Choose All Configurations as shown in Figure E.4 Figure E.4 Choosing All Configurations in the Build tab of the Target Info window Search for Other Linker Flags by entering “other linker” in the search box as shown in Figure E.5 Figure E.5 Searching for the Other Linker Flags Double-click on the value and change Cocoa to Foundation as shown in Figure E.6 Figure E.6 Changing from Cocoa configuration to Foundation 584 iPhone SDK Programming Search for GCC_PREFIX_HEADER as shown in Figure E.7 Figure E.7 Searching for the GCC_PREFIX_HEADER setting Double-click on the value field and clear the content as shown in Figure E.8 Figure E.8 Removing the GCC_PREFIX_HEADER setting Hit OK Close the target Info window E.3 The Model For demonstration purposes, we will use the Employee class as an example Let’s first add it to our targets Right-click on the Classes group Select Add > New File Select the NSObject subclass Name the class Employee as shown in Figure E.9 Make sure that you select both targets Replace the Employee.h header file with the contents shown in Listing E.1 Listing E.1 The Employee.h header file #import #import Unit Testing @interface Employee : NSObject { NSString *firstName, *lastName; NSInteger salary; Employee *manager; } @property(nonatomic, retain) NSString @property(nonatomic, retain) NSString @property(assign) NSInteger @property(nonatomic, retain) Employee @end *firstName; *lastName; salary; *manager; Figure E.9 Creating a new class and adding it to the two targets Replace the Employee.m implementation file with the contents shown in Listing E.2 Listing E.2 The Employee.m implementation file #import "Employee.h" @implementation Employee @synthesize firstName, lastName, salary, manager; -(void)setSalary:(NSInteger)theSalary{ 585 586 iPhone SDK Programming if(theSalary > 0){ salary = theSalary; } } -(void)dealloc{ self.manager = nil; self.firstName = nil; self.lastName = nil; [super dealloc]; } @end E.4 Writing Unit Tests for the Employee Class In this section, we show you how to write a unit test for the Employee class Create a new group in your project and call it something like Unit Testing Right-click on the new group and select Add > New File Select Cocoa and double-click on Objective-C test case class as shown in Figure E.10 Figure E.10 Creating a new Objective-C test case class Type the name of the new test case The name should end with TestCase See Figure E.11 Edit the SampleTestCase.h file and make it look like the following: #import #import "Employee.h" Unit Testing 587 @interface SampleTestCase : SenTestCase { } @end Figure E.11 Naming the test case Name must end with TestCase E.4.1 The setUp and tearDown methods Every unit test case has a setUp and a tearDown method In the setUp method, you put any initialization code you would like to be executed before executing the tests In the tearDown method, you provide any cleaning code that needs to be executed before finishing the tests This code is executed after all test have been executed In the example shown below, we have these methods as empty, but you are free to customize these as you see fit -(void)setUp{ //do any initialization here } - (void) tearDown{ // any cleaning up here } 588 iPhone SDK Programming E.4.2 Testing for equality To create a test, add a method in the test case class The method’s name needs to start with test The following method tests the Employee class initialization -(void)testSalaryShouldBeInitializedToZero{ Employee *emp = [[[Employee alloc] init] autorelease]; STAssertEquals(emp.salary, 0, @"Salary should be initialized to zero"); } The method starts by creating an Employee instance After that it asserts that the employee’s salary is initialized to The test uses the STAssertEquals macro This macro takes for the first two arguments C scalars, structs, or unions and an NSString instance for the third argument If the first two arguments are not equal, the test fails The following test tries to assign a negative value for the salary Since the Employee class rejects negative values, the existing value for salary remains unchanged The test checks for that -(void)testSalaryCannotBeNegative{ Employee *emp = [[[Employee alloc] init] autorelease]; NSInteger salary = emp.salary; [emp setSalary:-100]; STAssertEquals(emp.salary, salary, @"Setting salary to a negative value should be ignored"); } E.4.3 Testing for nullity You can use the STAssertNil macro to test for expected nil values The following test asserts that the manager property is set to nil -(void)testNullifyingManager{ Employee *emp = [[[Employee alloc] init] autorelease]; Employee *manager = [[[Employee alloc] init] autorelease]; emp.manager = manager; emp.manager = nil; STAssertNil(emp.manager, @"Should be able to nullify manager"); } Unit Testing 589 E.5 Adding a Build Dependency If you would like the unit tests to be executed and the success of the unit testing to be a condition to the successful building of your application, you can add a build dependency Edit the info of the main target of your application Click on the “+” icon to add a dependency, and add the unit test target as that dependency as shown in Figure E.12 Figure E.12 Adding a build dependency for the main target on the unit test target E.6 Running the Tests When you add the test target as a dependency for the main target of your application, running the test becomes an automatic task Every time you build your application, the tests are run Suppose someone, for some reason, removed the setSalary: method of the Employee class thinking that the @synthesize directive would generate one for us and there is no need for a custom setter 590 iPhone SDK Programming Now, the testSalaryCannotBeNegative test will fail with errors shown in the build console as shown in Figure E.13 Figure E.13 Build failure due to test failure Seeing that the test testSalaryCannotBeNegative has failed, you can inspect it as well as inspect the Employee class and quickly come to the conclusion that the setSalary: method was removed by someone You can then go to the version control software and pinpoint that person What happens next is beyond the scope of this text! ... 2008- 09- 04T17:47:28Z PI 3. 14000010 490 41748 RESULT Apple iPhone 3G Apple iPhone< /string> HTC Touch Diamond... parameters for the name attribute of the User entity Figure 19. 7 The User entity with three attributes Figure 19. 8 Adding a relationship in the User model 525 526 iPhone SDK Programming Figure 19. 9 Figure... setObject: [NSMutableArray arrayWithObjects:@ "Apple iPhone 3G", @ "Apple iPhone" , @"HTC Touch Diamond", nil] forKey:@"RESULT"]; [state setObject:[NSDate date] forKey:@"DATE"]; // save state of app [self