Re: Categories vs. subclassing
Re: Categories vs. subclassing
- Subject: Re: Categories vs. subclassing
- From: "Erik M. Buck" <email@hidden>
- Date: Tue, 26 Oct 2004 13:58:24 -0400
I will assume that readers are already familiar with Apple's
Objective-C manual that describes categories and add some more
references and excerpts from books.
http://www.stepwise.com/Articles/Technical/CategoricallySpeaking.html
http://www.stepwise.com/Articles/Technical/PosersAndCategories/
http://www.stone.com/The_Cocoa_Files/Categorically_Speaking.html
http://www.toodarkpark.org/computers/objc/ "OBJECT-ORIENTED PROGRAMMING
AND THE OBJECTIVE-C LANGUAGE"
http://www.cocoabuilder.com/search/archive/cocoa?words=category+subclass
http://www.gnustep.org/resources/ObjCFun.html
http://docs.sun.com/db/doc/802-2110/6i63kq4u0?a=view
http://www.google.com/search?
num=100&hl=en&lr=&as_qdr=all&q=category+subclass+objective-
c&btnG=Search
http://www.cocoaprogramming.net/Downloads.html includes the examples
from "Cocoa Programming" including Appendix A which among other things
shows how to call the original method implementation from a method
override in a category. "Cocoa Programming" Chapter 26 shows some nice
design time uses for categories and provides guidelines for sensible
usage. There are countless examples of category use in "Cocoa
Programming".
From "Cocoa Programming" Chapter 4
When categories were first introduced, NeXT recommended that they be
used to break large implementation files into several smaller files so
they could be used to organize the methods. For example, all the
private methods that should not be called except by the class’s author
can be organized into a category that is concealed from other
programmers. There is no way to restrict which methods of a class can
be called in which contexts, but methods can be hidden from the users
of a class. The extra effort to find out which hidden methods exist is
usually enough to discourage their use.
Categories containing private methods are often added within
implementation files so that there is no header file that declares the
methods, and they can still be used within the object’s own
implementation without warnings. In fact, category declarations do not
need an interface at all. Only the implementation is necessary.
Categories are useful for organizational purposes, but that barely
touches the power and flexibility enabled by them. Methods can be added
to any class without needing the source code for the class that is
extended, or recompiling. Categories are an alternative to subclassing
with some limitations. One limitation is that categories cannot be used
to add instance variables to a class the way a subclass can. [However,
it is possible to simulate adding instance variables] Nevertheless,
using categories is preferable to subclassing in many situations. For
example, suppose your application calls a method implemented by a Cocoa
framework class to obtain an object. The class of the object returned
by the framework was determined when the framework was written.
Subclassing the returned object won’t help because there might not be a
way to get the framework to return your subclass instead of the class
that was compiled into the framework. The class returned by the
framework can be extended by a category implemented in your code to add
the methods you need.
Methods added by a category can override existing implementations, and
it is possible to patch bugs in classes to which you have no source
code. To do so, replace the offending method with a correct
implementation in a category. A restriction when replacing methods is
that there is no convenient way to call the original implementation
from the overriding implementation. [However, Appendix A shows one way
to do it] Also, if more than one category implements the same method,
then it is unpredicitable which method will be chosen for use by the
runtime.
Methods that are implemented in a category can access all the extended
class’ instance variables directly. At runtime, methods that are
declared in a category are no different from methods declared in the
class interface. All subclasses of the extended class also gain the
category’s methods. Even preexisting instances gain the category’s
methods when code containing a category is dynamically loaded during a
program’s execution. It is possible to have an object that does not
understand certain messages when the application starts, but does
understand them after a plug-in containing a category has been loaded.
Categories are a powerful feature that can be easily abused. A good
practice is to add a unique prefix to the start of any method names
defined in categories that modify framework classes. The prefix reduces
the chance of an accidental clash with a hidden framework method or a
method in another category, which can happen easily. After a while,
programmers get in the habit of naming methods according to the
conventions used in Cocoa. If you think of a method to add to a Cocoa
class, there is a good chance someone else has thought of the same
method and given it the same name. Another danger is that Apple will
add the same method in a future release, but the method will be masked
by a preexisting category. Even if a method added via a category does
not create a conflict now, it may in a future version of Cocoa.
The as yet unreleased "Cocoa Design Patterns" includes a chapter on
associative storage that provides this example of a way to simulate
adding instance variables via a category and accessor methods. Here is
an excerpt from Chapter 9:
Simulating Instance Variables
One limitation of the Category pattern described in Chapter 8,
"Category", is that categories can only add methods to a class;
instance variables must be declared only in the main class interface.
This example shows one way the Associative Storage pattern is used to
simulate the addition of an instance variable to Cocoa’s NSObject
class. The category in this example provides access to a different
label for each instance of NSObject or any class that inherits from
NSObject. Instances that don't have an assigned label don't consume any
extra memory. The following category declares the –mySetLabel: and
–myLabel methods:
#import <Foundation/Foundation.h>
@interface NSObject (MYSimulateIVar)
- (void)mySetLabel:(NSString *)aString;
- (NSString *)myLabel;
@end
Methods like the ones defined in this category are called Accessors.
Accessors are themselves an important pattern described in Chapter 23.
The primary purpose of Accessors is to funnel all references to each
instance variable through a few – usually only two - methods. A nice
benefit of using accessors in this example is that even though the
labels are not stored as instance variables, programmers using the
NSObject class doesn't need to know that. The accessors shield users of
a class from the actual implementation.
#import "MYSimulateIvar.h"
@implementation NSObject (MYSimulateIVar)
//
static NSMapTable *_MYSimulatedIVarMapTable = NULL;
+ (NSMapTable *)_mySimulatedIVarMapTable
//
{
if(NULL == _MYSimulatedIVarMapTable)
{
_MYSimulatedIVarMapTable = NSCreateMapTable(
NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks,
16);
}
return _MYSimulatedIVarMapTable;
}
- (void)dealloc
//
{
NSMapRemove([[self class] _mySimulatedIVarMapTable], self);
}
- (void)mySetLabel:(NSString *)aString
//
{
NSString *newLabel = [aString copy];
NSMapInsert([[self class] _mySimulatedIVarMapTable], self, newLabel);
[newLabel release];
}
- (NSString *)myLabel
//
{
return NSMapGet([[self class] _mySimulatedIVarMapTable], self);
}
@end
There are several important elements to the implementation of the
MYSimulateIVar category. The +_myRefCountMapTable class method is used
to access the NSMapTable data structure that stores labels associated
with NSObject instances. The +_myRefCountMapTable method is not
declared in the category interface because it is a private
implementation detail of the category. The first time
+_myRefCountMapTable is called, the data structure is initialized to
store non-retained object keys and retained objects as values. It is
critical that the keys are not retained because if they are retained it
will be impossible to correctly deallocate any instances of NSObject
that have associated labels. The table is initialized with sufficient
storage for 16 key/value pairs, but that number is arbitrary. The
storage for the table automatically increases as keys and values are
added. The –dealloc method implemented in the category replaces
NSObject’s existing implementation. The –dealloc method removes any
key/value pair associated with an instance when the instance is
deallocated. It is safe to replace NSObject’s -dealloc implementation
in this case because the replaced version is documented to do nothing
at this time. If Apple ever changes the implementation of –dealloc in
the NSObject class, the fact that this category bypasses that
implementation my have undesirable side effects.
To flesh out support for labels associated with objects, it’s necessary
to provide encoding and decoding support so that labels are stored
along with any other data stored for objects when they are encoded. An
example of using existing accessors in the implementation of encoding
and decoding methods is provided in Chapter 13, “Archiving and
Unarchiving.” Support for copying labels when objects are copied should
also be supported, and a general technique is described in Chapter 14,
“Copying.”
Finally, this example is limited to storing of labels for objects. A
more useful category enables the storage on any amount of data with
each object. To enable that, modify the example to store dictionaries
of key/value pairs with –mySetUserInfo: and –myUserInfo methods instead
of instead of -mySetLabel: and –myLabel methods.
- (void)mySetUserInfo:(NSDictionary *)aDictionary
//
{
NSDictionary *newDictionary = [aDictionary copy];
NSMapInsert([[self class] _mySimulatedIVarMapTable], self,
newDictionary);
[newDictionary release];
}
- (NSString *)myUserInfo
//
{
return NSMapGet([[self class] _mySimulatedIVarMapTable], self);
}
Any number of key/value pairs can be stored in the dictionary
associated with each object. To keep the ability to store labels,
simply store a label string associated with a key such as @”Label” in
each user info dictionary.
Cocoa’s NSNotification class uses user info dictionaries to pass
arbitrary data along with each NSNotification instance.
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Cocoa-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden