Using Key-Value Programming

来源:互联网 发布:中国网络经纪人头像 编辑:程序博客网 时间:2024/06/05 02:06

Using Key-Value Programming

Now you will implement an example program that uses key-value programming to perform a variety of tasks. The program demonstrates the use of key-value coding and key-value observing in a system whose class diagram is depicted in Figure 18-2.

9781430250500_Fig18-02.jpg

Figure 18-2. Coders class diagram

In Xcode, create a new project by selecting New image Project . . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify Coders for the Product Name, chooseFoundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, selectNew Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

You’re going to create several classes here that declare various properties. SelectNew image File . . . from the Xcode File menu, select the Objective-C class template, and name the class Person. Select the Coders folder for the files location and theCoders project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the Person.h file and update the class interface, as shown in Listing 18-17.

Listing 18-17.  Person Class Interface

#import <Foundation/Foundation.h>@interface Person : NSObject@property (readonly) NSString *fullName;@property NSString *firstName;@property NSString *lastName;- (id)initWithFirstName:(NSString *)fname lastName:(NSString *)lname;@end

The interface declares three properties and a single initialization method. A Personhas a first name, last name, and full name.  The fullName property is read-only, and is derived from the firstName and lastName properties. Next, use the Xcode project navigator to select the Person.m file and code the implementation, as shown in Listing 18-18.

Listing 18-18.  Person Class Implementation

#import "Person.h"#define CodersErrorDomain   @"CodersErrorDomain"#define kInvalidValueError  1@implementation Person- (id)initWithFirstName:(NSString *)fname lastName:(NSString *)lname{  if ((self = [super init]))  {    _firstName = fname;    _lastName = lname;  }     return self;}- (NSString *)fullName{  return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];}- (BOOL)validateLastName:(id *)value error:(NSError * __autoreleasing *)error{  // Check for nil value  if (*value == nil)  {    if (error != NULL)    {      NSDictionary *reason = @{NSLocalizedDescriptionKey:                               @"Last name cannot be nil"};      *error = [NSError errorWithDomain:CodersErrorDomain                                   code:kInvalidValueError                               userInfo:reason];    }    return NO;  }  // Check for empty value  NSUInteger length = [[(NSString *)*value stringByTrimmingCharactersInSet:                       [NSCharacterSet whitespaceAndNewlineCharacterSet]] length];  if (length == 0)  {    if (error != NULL)    {      NSDictionary *reason = @{NSLocalizedDescriptionKey:                               @"Last name cannot be empty"};      *error = [NSError errorWithDomain:CodersErrorDomain                                   code:kInvalidValueError                               userInfo:reason];    }    return NO;  }  return YES;}+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key{  NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];  if ([key isEqualToString:@"fullName"])  {    NSArray *affectingKeys = @[@"firstName", @"lastName"];    keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];  }     return keyPaths;}@end

The initWithFirstName:lastName: method is used to initialize a Person instance with the input first and last names. The accessor method fullName returns the derived value of a Person object’s full name as the concatenation of the firstNameand lastName property values. The validateLastName:error: method is used to perform KVC property validation on the lastName property. It returns NO and anNSError object if the input last name is nil or an empty string; otherwise, it returns YES. The keyPathsForValuesAffectingValueForKey: method registers dependent keys. The method utilizes the fullName property, which depends on thefirstName and lastName properties, to obtain the keys that are added to the returned key path.

Next, you will implement the Coder class. Select New image File . . . from the Xcode File menu, select the Objective-C class template, and name the class Coder.Select the Coders folder for the files location and the Coders project as the target, and then click the Create button. In the Xcode project navigator pane, select theCoder.h file and update the class interface, as shown in Listing 18-19.

Listing 18-19.  Coder Class Interface

#import <Foundation/Foundation.h>@class Person;@interface Coder : NSObject@property Person *person;@property NSMutableArray *languages;@end

The class declares two properties: a Person property named person, and anNSMutableArray property named languages. The languages property is an array that contains the names of programming languages that a Coder uses. Notice that a @class directive is used to forward declare the Person class, thereby removing the need to import its header file in this interface. Now use the Xcode project navigator to select the Coder.m file and code the implementation, as shown inListing 18-20.

Listing 18-20.  Coder Class Implementation

#import "Coder.h"@implementation Coder- (NSUInteger)countOfLanguages{  return [self.languages count];}- (NSString *)objectInLanguagesAtIndex:(NSUInteger)index{  return [self.languages objectAtIndex:index];}- (void)insertObject:(NSString *)object inLanguagesAtIndex:(NSUInteger)index{  [self.languages insertObject:object atIndex:index];}- (void)removeObjectFromLanguagesAtIndex:(NSUInteger)index{  [self.languages removeObjectAtIndex:index];}- (void)observeValueForKeyPath:(NSString *)keyPath                      ofObject:(id)object                        change:(NSDictionary *)change                       context:(void *)context{  NSString *newValue = change[NSKeyValueChangeNewKey];  NSLog(@"Value changed for %@ object, key path: %@, new value: %@",        [object className], keyPath, newValue);}@end

The Coder implementation includes the KVO-recommended methods for the ordered, to-many relationship of the languages property. TheobserveValueForKeyPath:ofObject:change:context: method is executed when aCoder property with the input key path is changed. In this case, the method just prints out the new value for the object and key path in question.

Finally, you will implement the Coders class. As depicted in Figure 18-2, the Codersclass holds a collection (an NSSet) of Coder instances. Select New image File . . . from the Xcode File menu, select the Objective-C class template, and name the classCoders. Select the Coders folder for the files location and the Coders project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the Coders.h file and update the class interface, as shown in Listing 18-21.

Listing 18-21.  Coders Class Interface

#import <Foundation/Foundation.h>@class Coder;@interface Coders : NSObject@property NSSet *developers;@end

The Coders interface declares a single property, an NSSet named developers that contains the collection of Coder objects. Now use the Xcode project navigator to select the Coders.m file and code the implementation, as shown in Listing 18-22.

Listing 18-22.  Coders Class Implementation

#import "Coders.h"@implementation Coders- (NSUInteger)countOfDevelopers{  return [self.developers count];}- (NSEnumerator *)enumeratorOfDevelopers{  return [self.developers objectEnumerator];}- (Coder *)memberOfDevelopers:(Coder *)member object:(Coder *)anObject{  return [self.developers member:anObject];}@end

The Coders implementation includes the KVO-recommended methods for the unordered, to-many relationship of the developers property.

Now that you have finished implementing the classes, let’s move on to the main()function. In the Xcode project navigator, select the main.m file and update themain() function, as shown in Listing 18-23.

Listing 18-23.  Coders main( ) function

#import <Foundation/Foundation.h>#import "Person.h"#import "Coder.h"#import "Coders.h"int main(int argc, const char * argv[]){  @autoreleasepool  {    Person *curly = [[Person alloc] initWithFirstName:@"Curly" lastName:@"Howard"];    NSLog(@"Person first name: %@", [curly valueForKey:@"firstName"]);    NSLog(@"Person full name: %@", [curly valueForKey:@"fullName"]);    NSArray *langs1 = @[@"Objective-C", @"C"];    Coder *coder1 = [Coder new];    coder1.person = curly;    coder1.languages = [langs1 mutableCopy];    NSLog(@"\nCoder name: %@\n\t  languages: %@",          [coder1 valueForKeyPath:@"person.fullName"],          [coder1 valueForKey:@"languages"]);         Coder *coder2 = [Coder new];    coder2.person = [[Person alloc] initWithFirstName:@"Larry" lastName:@"Fine"];    coder2.languages = [@[@"Objective-C", @"C++"] mutableCopy];    NSLog(@"\nCoder name: %@\n\t  languages: %@",          [coder2 valueForKeyPath:@"person.fullName"],          [coder2 valueForKey:@"languages"]);         [curly addObserver:coder1            forKeyPath:@"fullName"               options:NSKeyValueObservingOptionNew               context:NULL];    curly.lastName = @"Fine";    [curly removeObserver:coder1 forKeyPath:@"fullName"];         Coders *bestCoders = [Coders new];    bestCoders.developers = [[NSSet alloc] initWithArray:@[coder1, coder2]];    [bestCoders valueForKey:@"developers"];    NSLog(@"Number of coders = %@", [bestCoders.developers                                     valueForKeyPath:@"@count"]);    NSError *error;    NSString *emptyName = @"";    BOOL valid= [curly validateValue:&emptyName forKey:@"lastName" error:&error];    if (!valid)    {      NSLog(@"Error: %@", ([error userInfo])[NSLocalizedDescriptionKey]);    }  }  return 0;}

The function first creates a Person instance and then uses key-value coding to retrieve values for its properties. Next, a Coder instance is created, and its personand languages property are set. The key-value coding APIs are then used to retrieve the corresponding property values. Another Coder instance is then created and its property values are set accordingly. Key-value observing is then demonstrated, with a Coder instance registered to receive notifications when thefullName property of a Person instance is changed. A Coders instance (remember, the Coders class holds a collection of Coder instances) is then created and itsdevelopers property is assigned to the collection of Coder instances created previously. A collection operator is then used to return the total number of coders in the collection. Finally, key-value coding validation is demonstrated on thelastName property of a Person instance.

When you compile and run the program, you should observe the messages in the output pane shown in Figure 18-3.

9781430250500_Fig18-03.jpg

Figure 18-3. Coders program output

Excellent! This completes your in-depth examination of the key-value programming APIs and infrastructure. Knowledge of key-value coding and key-value observing will be extremely useful to you when programming with Objective-C and using Apple’s various software frameworks and services.

原创粉丝点击