本节讲Objective-C中使用category,来给现有类增加新的方法。
Categories Add Methods to Existing Classes
If you need to add a method to an existing class, perhaps to add functionality to make it easier to do something in your own application, the easiest way is to use a category.
The syntax to declare a category uses the @interface keyword, just like a standard Objective-C class description, but does not indicate any inheritance from a subclass. Instead, it specifies the name of the category in parentheses, like this:
@interface ClassName (CategoryName)
@end
A category can be declared for any class, even if you don’t have the original implementation source code (such as for standard Cocoa or Cocoa Touch classes). Any methods that you declare in a category will be available to all instances of the original class, as well as any subclasses of the original class. At runtime, there’s no difference between a method added by a category and one that is implemented by the original class.
Consider the XYZPerson class from the previous chapters, which has properties for a person’s first and last name. If you’re writing a record-keeping application, you may find that you frequently need to display a list of people by last name, like this:
Appleseed, John
Doe, Jane
Smith, Bob
Warwick, Kate
Rather than having to write code to generate a suitable lastName, firstName string each time you wanted to display it, you could add a category to the XYZPerson class, like this:
#import "XYZPerson.h"
@interface XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)lastNameFirstNameString;
@end
In this example, the XYZPersonNameDisplayAdditions category declares one additional method to return the necessary string.
A category is usually declared in a separate header file and implemented in a separate source code file. In the case of XYZPerson, you might declare the category in a header file called XYZPerson+XYZPersonNameDisplayAdditions.h.
Even though any methods added by a category are available to all instances of the class and its subclasses, you’ll need to import the category header file in any source code file where you wish to use the additional methods, otherwise you’ll run into compiler warnings and errors.
The category implementation might look like this:
#import "XYZPerson+XYZPersonNameDisplayAdditions.h"
@implementation XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)lastNameFirstNameString {
return [NSString stringWithFormat:@"%@, %@", self.lastName, self.firstName];
}
@end
Once you’ve declared a category and implemented the methods, you can use those methods from any instance of the class, as if they were part of the original class interface:
#import "XYZPerson+XYZPersonNameDisplayAdditions.h"
@implementation SomeObject
- (void)someMethod {
XYZPerson *person = [[XYZPerson alloc] initWithFirstName:@"John"
lastName:@"Doe"];
XYZShoutingPerson *shoutingPerson =
[[XYZShoutingPerson alloc] initWithFirstName:@"Monica"
lastName:@"Robinson"];
NSLog(@"The two people are %@ and %@",
[person lastNameFirstNameString], [shoutingPerson lastNameFirstNameString]);
}
@end
As well as just adding methods to existing classes, you can also use categories to split the implementation of a complex class across multiple source code files. You might, for example, put the drawing code for a custom user interface element in a separate file to the rest of the implementation if the geometrical calculations, colors, and gradients, etc, are particularly complicated. Alternatively, you could provide different implementations for the category methods, depending on whether you were writing an app for OS X or iOS.
Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class.
The only way to add a traditional property—backed by a new instance variable—to an existing class is to use a class extension, as described in Class Extensions Extend the Internal Implementation.
Note: Cocoa and Cocoa Touch include a variety of categories for some of the primary framework classes.
The string-drawing functionality mentioned in the introduction to this chapter is in fact already provided for NSString by the NSStringDrawing category for OS X, which includes the drawAtPoint:withAttributes: and drawInRect:withAttributes: methods. For iOS, the UIStringDrawing category includes methods such as drawAtPoint:withFont: and drawInRect:withFont:.