Adventures in the transition from C to Cocoa.

Saturday, June 16, 2007

Object Antics

As with all Object-Oriented languages, Objective-C allows for some pretty handy trickery when it comes to objects, inheritance, and extensions. The terms used are fairly exclusive, but most of them have parallels.

In Cocoa Objects I briefly went over how to do basic inheritance. It is done by adding : InheritedObject on the @interface line, like this:

@interface myObject: NSObject


Objective-C does not allow multiple-inheritance. This means each object can only inherit from one object, and has only one super class, which we'll get into later.

Related to inheritance is an idea Objective-C refers to as Protocols. A protocol is comparable to a virtual class in C++, in that it defines what methods need to exist, but doesn't define how they work.

A protocol is defined very simply, as follows:
@protocol myProtocol
-(void) aMethod;
-(void) anotherMethod;
@end


For an object to adhere to this protocol, it must implement two methods, called aMethod and anotherMethod.

To indicate that an object adheres to a protocol, we use code like this:
@interface myClass: <myProtocol>


The stuff between the less-than and greater-than signs defines the protocol or protocols. If there are multiple protocols an object adheres to, they all appear between the brackets, in a comma-separated list, like this:
@interface myClass: <myProtocol, anotherProtocol>


Objects are meant to be dynamic in Objective-C. As such, NSObject provides a number of handy methods to help figure out what class an object inherits and what methods it uses.

#import <Foundation/Foundation.h>

@interface basicClass: NSObject
{
int basicInt;
}
@end

@interface mediumClass: basicClass
{
int mediumInt;
}
@end

@interface complexClass: mediumClass
{
int complexInt;
}
@end

@implementation basicClass
@end

@implementation mediumClass
@end

@implementation complexClass
@end

int main()
{
basicClass *bcObj = [[basicClass alloc] init];
mediumClass *mdObj = [[mediumClass alloc] init];
complexClass *cxObj = [[complexClass alloc] init];

if( [cxObj isKindOfClass: [mediumClass class]] == YES )
printf("cxObj is a kind of mediumClass\n");
else
printf("cxObj is not a kind of mediumClass\n");

if( [cxObj isKindOfClass: [basicClass class]] == YES )
printf("cxObj is a kind of basicClass\n");
else
printf("cxObj is note a kind of basicClass\n");

if( [bcObj isKindOfClass: [mediumClass class]] == YES )
printf("bcObj is a kind of mediumClass\n");
else
printf("bcObj is not a kind of mediumClass\n");

return 0;
}


As you can probably expect from this example, the output is this:
cxObj is a kind of mediumClass
cxObj is a kind of basicClass
bcObj is not a kind of mediumClass


We can use the isKindOfClass method to see if an object we get is of a particular kind. We can use the class method to get an object's class.

A related method is isMemberOfClass, which is used in exactly the same way as isKindOfClass. While isKindOfClass will return YES if any parent object is the specified class, isMemberOfClass will only return YES if the object in question's immediate parent (the super class) is the specified class. If we replace all all occurrences of isKindOfClass with isMemberOfClass in the example above, the output becomes:
cxObj is not a member of mediumClass
cxObj is note a member of basicClass
bcObj is not a member of mediumClass


Notice how complexClass is not a member of basicClass because basicClass is a grandparent object, not an immediate parent of complexClass.

There are a few other methods related to object methods, but they use selectors so I'll address those in a future entry. There are also methods we can use to determine if an object conforms to a given protocol. This will also be addressed later.

Up to this point, we've only dealt with @private for member variables, not methods. In fact, if you got adventurous and tried to mark some methods as private, you probably ran into some problems. This is because Objective-C has no syntax to create private member functions. The way around this is by using Categories. Categories are used to extend the functionality of a class by adding new methods. Simple usage looks like this:

#import 

@interface basicClass: NSObject
{
int basicInt;
}
@end

@implementation basicClass
@end

@interface basicClass (Extension)
-(void)extendedMethod;
@end

@implementation basicClass (Extension)
-(void)extendedMethod
{
printf("This method is in a category called \"Extension\"\n");
}
@end

int main()
{
basicClass *bcObj = [[basicClass alloc] init];

[bcObj extendedMethod];

return 0;
}


The (Extension) part defines the Category, which is called "Extension" in this case. The name can be pretty much anything though. However, each category name must be unique. Also, categories cannot add member variables, only methods.

Predictably, the output of the Category example above is:
This method is in a category called "Extension"


So, to create private member functions, all you need to do is add a Private category in the object's .m source file instead of the .h. This keeps the method inaccessible to callers outside of that .m file.

One last object trick available in Objective-C is the ability for a class to pose as its super class. This feature is aptly named "Posing." To have a class pose as another class, we use the poseAsClass method.
#import <Foundation/Foundation.h>

@interface basicClass: NSObject
{
int basicInt;
}
-(void) print;
@end

@implementation basicClass
-(void) print
{
printf("This is the basicClass method, uncooked.\n");
}
@end

@interface anotherClass: basicClass
@end

@implementation anotherClass
-(void) print
{
printf("This is anotherClass method.\n");
}
@end

int main()
{
basicClass *bcObj = [[basicClass alloc] init];
anotherClass *aObj = [[anotherClass alloc] init];

// after this, everything using basicClass will
// actually use anotherClass
[anotherClass poseAsClass: [basicClass class]];

basicClass *basicObject = [[basicClass alloc] init];

[bcObj print];
[aObj print];
[basicObject print];

return 0;
}


The output, demonstrating posing, looks like this:
This is the basicClass method, uncooked.
This is anotherClass method.
This is anotherClass method.


As you can see, the last two objects use the same print method, despite being different classes. This happens because of posing.

Categories