Benedict's Soapbox

The Objective-C Class Object

Let’s start by defining some terms as used in Objective-C:

Object:
An object is a construct which groups related data and functionality.
Class:
A class is an object thats primary role is to provide a template of data and functionality. Not all object orientated languages are class-based. Javascript, for example, is prototype-based.
Instance:
An instance is a concrete occurrence of a class. When discussing object-orientated programming the term 'object' is often used when 'instance' would be more precise. All instances are objects, but not all objects are instances.

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:

  1. Objective-C does not have class variable. This is easy to work around; we simply declare class variables in the global scope. By putting the variables inside a struct we avoid cluttering the global scope (it’s bad manners to use the global scope more than strictly necessary).
  2. We have to be careful when accessing variables directly compared to when using generated accessors. Generated accessors are ‘atomic’ by default so are thread safe. Its feasible for 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.