Subclassing
Often, the default behavior or appearance of an object supplied by Cocoa won’t be quite what you’re after, and you’ll want to customize it.Nevertheless, sometimes setting properties and calling methods won’t suffice to customize an instance the way you want to. In such cases, Cocoa may provide methods that are called internally as an instance does its thing, and whose behavior you can customize by subclassing and overriding. You don’t have the code to any of Cocoa’s built-in classes, but you can still subclass them, creating a new class that acts just like a built-in class except for the modifications you provide.
A common case involves custom drawing into a UIView. You don’t actually draw
into a UIView; rather, when a UIView needs drawing, its drawRect: method is called so that the view can draw itself. So the way to make a UIView that is drawn in some completely custom manner is to subclass UIView and implement drawRect: in the subclass. As the documentation says, “Subclasses override this method if they actually draw their views.” That’s a pretty strong hint that you need to subclass UIView in order to do custom drawing into a UIView.
For example, suppose we want our window to contain a horizontal line. There is no horizontal line interface widget, so we’ll just have to roll our own — a UIView that draws itself as a horizontal line. Let’s try it. First we’ll code the class:
1. In our Empty Window example project, choose File → New → New File and specify a Cocoa Touch Objective-C class, and in particular a subclass of UIView. Call it MyHorizLine. Xcode creates MyHorizLine.m and MyHorizLine.h.
2. In MyHorizLine.m, remove the comment delimiters from around the drawRect: implementation, and make it look like this:
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(c, 0, 0);
CGContextAddLineToPoint(c, self.bounds.size.width, 0);
CGContextStrokePath(c);
}
3. Edit MainWindow.xib. Show the Window top-level object in the canvas. Find UIView in the Object library, and drag it into the Window object in the canvas.
4. Select the UIView in the window and use the Identity inspector to change its class to MyHorizLine.
You wouldn’t subclass UIApplication (the class of the singleton shared application instance) just in order to respond when the application has finished launching, because the delegate mechanism (Chapter 11) provides a way to do that (application:didFinishLaunchingWithOptions:). On the other hand, if you need to perform certain tricky customizations of your app’s fundamental event messaging behavior, you’d have to subclass UIApplication in order to override sendEvent:. The documentation does tell you this, and it also tells you, rightly, that needing to do this would be fairly rare (though I have had occasion to do it).
If you do subclass UIApplication, you’ll need to change the third argument in the call to UIApplicationMain in main.m from nil to the NSString name of your subclass. Otherwise your UIApplication subclass won’t be instantiated as the shared application instance.
Categories
A category is an Objective-C language feature that allows you to reach right into an existing class and define additional methods. You can do this even if you don’t have the code for the class, as with Cocoa’s classes. Your instance methods can refer to self, and this will mean the instance to which the message was originally sent, as usual.
A category, unlike a subclass, cannot define additional instance variables; it can override methods, but you should probably not take advantage of this ability.
For example, in one of my apps I found myself performing a bunch of string transformations in order to derive the path to various resource files inside the app bundle based on the resource’s name and purpose. I ended up with half a dozen utility methods. Given that these methods all operated on an NSString, it was appropriate to implement them as a category of NSString, thus allowing any NSString, anywhere in my code, to respond to them.
The code was structured like this (I’ll show just one of the methods):
// [StringCategories.h]
#import <Foundation/Foundation.h>
@interface NSString (MyStringCategories)
- (NSString*) basePictureName;
@end
// [StringCategories.m]
#import "StringCategories.h"
@implementation NSString (MyStringCategories)
- (NSString*) basePictureName {
return [self stringByAppendingString:@"IO"];
}
@end
A category is particularly appropriate in the case of a class like NSString, because the documentation warns us that subclassing NSString is a bad idea. That’s because NSString is part of a complex of classes called a class cluster, which means that an NSString object’s real class might actually be some other class. A category is a much better way to modify a class within a class cluster than subclassing.