
15 Most Recent [RSS]
More...
|
Classes are objects, too!
One of the things that confuses people about Objective-C, is the concept of a class method. Many people think that class methods are essentially functions with Objective-C method syntax, like class methods in C++ are. But that's not true.
So, what are class methods? Well, if I wanted to confuse everybody reading this, I'd probably say something like "class methods in Objective C are exactly the same as instance methods". While this is true from a low-level standpoint, if that were true on a higher level, we wouldn't be making the distinction.
So, what's the deal with class methods?
Well, in Objective-C, classes are actually also objects. The class of these objects is "Metaclass". Confused yet? Well, until last year's German Macoun conference, I sure was. Then Amin Negm-Awad explained it to me in his session on Objective C.
Maybe it helps to keep in mind that all method calls work the same way: The object gets asked what its class is, and the class gets asked whether it implements this method for its instances. If it doesn't, the superclass gets asked the same question, and so on, until the proper (inherited) method is found in one of the superclasses. If a method was found, it then gets called, otherwise it fails (after a short attempt to let the object provide a fallback using -forwardInvocation: and the likes).
When the designers of Objective-C added class methods to the language, they had two options:
- Reimplement all that for class methods again.
- Make a class look just like another object to the code that takes care of this.
They went with the latter. They invented a special class called "metaclass" to stand in for the typical superclass, and made the compiler automatically create an object for each class you declare.
How are class methods called?
Why did they do that? That's easy: Because that way, a subclass can override the class methods of its superclass. Yes, you heard right, if your base class has a method:
+(id) factoryMethod
{
MyBaseClass* newObject = [[[MyBaseClass alloc] init] autorelease];
return newObject;
}
you can just replace that in your subclass with a method like
+(id) factoryMethod
{
MySubClass* newObject = [[[MySubClass alloc] init] autorelease];
return newObject;
}
But you probably knew that. What you maybe didn't know, is that "self" in class methods points at the class object. In our first case, that means we could actually write the first +factoryMethod above as:
+(id) factoryMethod
{
MyBaseClass* newObject = [[[self alloc] init] autorelease];
return newObject;
}
The behaviour is exactly the same in the base class, when called as myVar = [MyBaseClass factoryMethod]. But in the subclass? Well, if you call this method on the subclass, as in myVar = [MySubClass factoryMethod], the same happens as would happen in a regular instance method call: The object at the start of the square brackets is the target and ends up in "self". Just in this case, this is not an instance. It is the class object MySubClass itself.
If we don't override +factoryMethod, the method we inherited from MyBaseClass gets called. With self being MySubClass, what gets executed is equivalent to:
+(id) factoryMethod
{
MySubClass* newObject = [[[MySubClass alloc] init] autorelease];
return newObject;
}
since self is MySubClass here. Cool, isn't it?
Throw in some introspection magic
People coming from other languages occasionally write code like the following:
+(void) showModalViewControllerForClass: (NSString*)className
{
NSViewController* viewController = [[[NSClassFromString(className) alloc] initWithNibName: className] autorelease];
[viewController showModal];
}
And then call it like
[MyModalViewControllerBaseClass showModalViewControllerForClass: @"MyModalViewControllerSubClass"];
Now, it's cool that Objective-C lets you convert between strings and classes at all, but after my article on defensive coding, everyone should know that it is a mistake to pass a string literal instead of defining a constant. The compiler does not know that this particular string constant should contain a valid type name, so if you have a spelling error in the string, NSClassFromString returns Nil and this'll quietly fail.
But before you go defining a constant so there's only one place where you can mis-spell this string, think harder. There already is an identifier for this class. Yep, that's right, you can just use [MyModalViewControllerSubClass class] to get the class object.
Okay, so we rewrite the method to:
+(void) showModalViewControllerForClass: (Class)theClass
{
NSViewController* viewController = [[[theClass alloc] initWithNibName: NSStringFromClass(theClass)] autorelease];
[viewController showModal];
}
and call it as
MyModalViewControllerBaseClass showModalViewControllerForClass: [MyModalViewControllerSubClass class]];
right? It may be terribly verbose, but Uli has explained why it's better code.
But that's still bad code. As we learned above, every method called has a self pointer pointing to the target. In the case of class methods, this points to our class. So what we can do is rewrite this to be
+(void) presentModal
{
UIViewController* viewController = [[[self alloc] initWithNibName: NSStringFromClass(self)] autorelease];
[viewController showModal];
}
Of course, we also need to change our calls. Instead of calling the method on the base class, what we do instead is call it on the subclass:
[MyModalViewControllerSubClass presentModal];
And with two identifiers, one of which is a shorter, more concise method name, we do what we were doing with a string, two class names and a long method name, or, to avoid the string, two nested method calls.
Objective-C is full of such subtle goodies. So the next time you think something like "class methods are pointless", "why do I have to pass strings" etc., look at the Objective-C language documentation. Chances are that there's some neat feature you can find that, among other things, solves your problem much more easily for you.
So, why confuse us with all that "classes are objects" junk?
So far, I mainly talked about self and class methods, but there is one other thing you should take along from that whole "classes are objects" business: they really are objects. What that means is that you can store them in arrays or dictionaries, get them back out again and call methods on them. That you can send methods to them (calling class methods) and self gets set to the class is really just one useful side effect.
Have a file export plugin architecture? Load the plugin bundles and build an NSDictionary mapping each plugin's file type to the bundle's principalClass. Writing an interpreter? Build an array of your different instruction classes and instantiate them as needed... there might be hundreds of other opportunities to use classes as objects.
Michelle writes: I *think* I followed you through all of that. But there is one oddity of Objective-C I still don't get.
Let's say I have a class method:
+ (void) thankUli: (NString*) howMuch { }
Why do I get no compiler error when I mistakenly invoke it as below?
[self thankUli: @"profusely"];
I've had some very subtle bugs from this issue. I like to use class methods to improve encapsulation, but it's very easy to just type "self" without thinking.
|
Ahruman writes: ‘The superclass of these objects is "Metaclass".’
No no, not at all. The _class_ of a class object is a metaclass. The superclass of a class object is the class of the superclass of the class. Classy, innit? ;-)
|
Adam writes: Good article there. Sorry, I just started reading your blog again after a long time.
One tiny thing though:
-forwardInvoction: only gets called if the object and it's superclassses do not respond to the selector, i.e., you are only given a chance to forward when it would otherwise give up and raise an NSInvalidArgument exception (in the NSObject case). I imagine this is for performance reasons; it would be pointlessly innefficent to have to construct an NSInvoction object, send -methodSignitureForSelector: and -forwardInvoction: every time you call a method.
|
Karsten writes: Michelle: if you call [self thankUli: @"profusely"] from a class method then this is just alright...if you call it from an instance method you'll get a warning that this method does not exist. Then you could do [[self class] thankUli...].
Uli: one thing i still miss in Objective C: the classes are objects, but there's no way in the syntax to define instance variables for classes, while the runtime already supports them, i think.
|
Uli Kusterer replies: ★ Adam,
I forgot an "otherwise it fails there", thanks for the correction. I think I poured a steaming hot cup of typoes over this article :-(
|
Jean-Daniel Dupas writes: "class methods in Objective C are exactly the same as instance methods"
My own version of this sentence to confuse people is "class methods are instance methods of class objects" ;-)
One feature of this design that is often overlooked is the ability to use a Class in all methods that take an object and a selector as arguments (notification observer, delegate, etc…).
Just pass the Class object and the selector of a class method, and it works.
|
xebecs writes: Karsten: Thanks for a bit of extra information. Now I realize what I was doing. I still think the compiler may not be giving a warning when it should.
@interface MyWebViewController: UIViewController <UIWebViewDelegate>
{ blah }
+ (UIView*) makeWebView
{
UIWebView *webView = // alloc, init
webView.delegate = self;
return [webView autorelease];
}
While an object of this type is a UIWebViewDelegate, the class is NOT! ...is it?
If not, why doesn't the compiler warn about the MyWebViewController class not being a UIWebViewDelegate?
[This is the actual problem I had -- I was misremembering from 3 projects back.]
|
charles Parnot writes: Karsten: the problem is that all class objects are instances of the same class 'MetaClass'. Thus, all class objects for all the classes in your project would need to have the same instance variables.
Of course, at this stage, you could argue that class objects could be instances of MetaClass subclasses, and thus define their own instance variables (note that by nature, they would be singleton instances). But that would mean rewriting some of the basics of the runtime and the compiler.
That would solve the behind-the-scenes stuff. We would still need some Objective C syntax to accomodate class variables. Again, more work on the compiler.
Until that happens, we can just use plain C and static variables!
|
Matthieu Cormier writes: Under,
"...what gets executed is equivalent to:"
MySubClass* newObject = [[[MySubClass alloc] init] autorelease];
could probably be...
MyBaseClass* newObject = [[[MySubClass alloc] init] autorelease];
for clarity. |
|  |