Let’s start by defining some terms as used in Objective-C:
The significant point here is that classes are objects:
NSLog(@"Does an NSString instance conform to NSObject?: %@", ([@"I'm a string" conformsToProtocol:@protocol(NSObject)]) ? @"Yes" : @"No");
//Does an NSString instance conform to NSObject?: Yes
NSLog(@"Does the NSString class conform to NSObject?: %@", ([[@"I'm a string" class] conformsToProtocol:@protocol(NSObject)]) ? @"Yes" : @"No");
//Does the NSString class conform to NSObject?: Yes
To learn more about Objective-C instances, classes and meta classes read What is a meta-class in Objective-C?, the Objective-C 2.0 Runtime Programming Guide and The Objective-C 2.0 Programming Language.
How is this useful? In Apples documentation the term “receiver” is when describing methods. Here’s a sample from the NSKeyValueObserving Protocol:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
This message is sent to the receiver when the value at the specified key path relative to the given object has changed.
The method is listed as a instance method. However, the runtime does not care if the receiver is an instance object or a class object; the runtime only cares that the receiver responds to the message it was sent. Therefore we can apply this method to a class object as well as to an instance object:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context //makes instance objects respond to the message
+ (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context //makes the class object respond to the message
Defining the same method for instances and the class is perfectly legal:
Although it’s not a common practice, you can define a class method and an instance method with the same name.
So how can this be used in practice? Consider the following situation: We are creating an app that lists biscuits. The type of biscuits available changes frequently. The biscuit manufacture provides a web service that lists all of the biscuits that are currently available. By using the approach outlined above we can encapsulate the updating of the biscuit types within the biscuit class and keep all the implementation details of the biscuit in the model without needing to involve the controller:
//EMKBiscuit.m
struct
{
EMKBiscuitUpdateOperation *updateOperation; //EMKBiscuitUpdateOperation is an NSOperation subclass. We could implement all the methods on the class object and use an NSInvocationOperation but that would be get messy.
} EMKBiscuitClassStorage;
@implementation EMKBiscuit
…
+(void)load
{
[self setupUpdateOperation];
}
+(void)setupUpdateOperation
{
@synchronized(self)
{
if (EMKBiscuitClassStorage.updateOperation) return; //there is already an active update operation
NSAutoReleasePool *pool = [NSAutoReleasePool new];
EMKBiscuitClassStorage.updateOperation = [EMKBiscuitUpdateOperation new];
[EMKBiscuitClassStorage.updateOperation addObserver:self forKeyPath:@"isFinished" options:0 context:NULL];
[[NSOperationQueue EMK_defaultQueue] addOperation:EMKBiscuitClassStorage.updateOperation]; //See EMKPantry (https://github.com/BenedictEMK/EMKPantry) for the EMK_defaultQueue category.
[pool release];
}
}
+(void)tearDownUpdateOperation
{
NSAutoReleasePool *pool = [NSAutoReleasePool new];
@synchronized(self)
{
[EMKBiscuitClassStorage.updateOperation removeObserver:self];
[EMKBiscuitClassStorage.updateOperation release];
EMKBiscuitClassStorage.updateOperation = nil;
}
[self performSelector:@selector(setupUpdateOperation) afterDelay: 1800]; //1800 = 30 (minutes) * 60 (seconds)
[pool release];
}
+(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context //makes the class respond to the message
{
if (object == EMKBiscuitClassStorage.updateOperation && [keyPath isEqualToString:@"isFinished"])
{
[self tearDownUpdateOperation];
}
}
//…
@end
A few notes about the code:
setupUpdateOperation
and tearDownUpdateOperation
to run concurrently, therefore we wrap them in a @synchronized
block to avoid the issue. (KVO are sent on the thread that generates them. It is unlikely that the isFinished
KVO of the NSOperation
will be called on the main thread).In summary Objective-C makes it very easy to use a class object as a singleton.