Don't Retain That Class!

Let's take a look at a rather unsuspecting method.

- (void)setClass:(Class):class
          forKey:(NSString *)key;

There isn't much special about it. Most likely it belongs to some object that plays some dictionary-like role, but that doesn't really matter. What does is that I would have a hard time sleeping at night if I wrote that.

I'm a pedant. Let's sprinkle it with some magic pedant pixy dust and see what happens.

- (void)setClass:(Class __unsafe_unretained):class
          forKey:(NSString *)key;

Ah, so much better. I'll sleep well tonight, or at least the pedant within.

Class Singeltons

In Objective-C classes are objects and more so, they are global singletons. Every instance of NSString will return the same object when you call the -class method, the NSString class object. Getting ahold of these singletons are quite easy. You can send the +class method to the class or use objc_getClass from the runtime.

Class a = [NSString class];

#import <objc/runtime.h>
Class b = objc_getClass("NSString");

These both return the same object. Life is great.

Retaining a Class

Let's give it a try!

NSArray *array = @[ [NSString class] ];

This code executes without a hitch. NSArray retains all object in it and thus we have successfully retained a class object. I would normally just call -retain on it but hey, MRR is dead to me.

Here's another common pattern:

@interface MonkeyObject : NSObject
@property (nonatomic, strong) Class class;
@end

Obviously assigning any class to the property will retain it since it is qualified with strong ownership. But is it safe?

It Isn't Safe

Any code that retains or releases a class can be unsafe depending on the actual class that is passed to it at run-time! Think for a moment, where does the class get the retain method in the first place?

NSLog(@"%@", [[NSString class] isKindOfClass:[NSObject class]] ? @"Yes" : @"No");

This prints "YES". The NSString class object is a subclass of NSObject. It is a subclass of its root class. That is where the -retain and -release methods come from (although it overrides them to no-op).

Let's make a class of our own.

NS_ROOT_CLASS
@interface AbsolutelyNothing
@end

Not to spoil the fun, but you can't retain the first one! It doesn't have the retain method, obviously. Let's try to put it in an array.

NSArray *array = @[ objc_getClass("AbsolutelyNothing") ];

We have to use objc_getClass because AbsolutelyNothing clearly doesn't implement the -class method, heh.

2014-03-14 10:20:58.436 Untitled[54459:507] *** NSForwarding: warning: object 0x10ce0f1c0 of class 'AbsolutelyNothing' does not implement methodSignatureForSelector: -- trouble ahead
2014-03-14 10:20:58.436 Untitled[54459:507] *** NSForwarding: warning: object 0x10ce0f1c0 of class 'AbsolutelyNothing' does not implement doesNotRecognizeSelector: -- abort
Run Command: line 1: 54459 Trace/BPT trap: 5       ./"$2" "${@:3}"

That's some mighty ugly vomit. Apparently we tried to send it a method it doesn't implement. Well duh, it doesn't implement any methods. If we want to invoke a selector on a class that it doesn't implement then the minimum we must do is implement methodSignatureForSelector: (due to the implementation of the forwarding path). Let's implement that just to see what it is trying to invoke.

NS_ROOT_CLASS
@interface AbsolutelyNothing
@end

@implementation AbsolutelyNothing
- (void)methodSignatureForSelector:(SEL):aSelector
{
  NSLog(@"Don't tell me to %@!", NSStringFromSelector(aSelector));
}
@end

...

NSArray *array = @[ objc_getClass("AbsolutelyNothing") ];

And when we run it...

2014-03-14 10:21:59.368 Untitled[54459:507] Don't tell me to retain!

See, I told you not to!

Unsafe-Unretain that Class!

Excellent grammar, right? :-P

All classes should be marked as unsafe-unretain when used as properties or passed as arguments to functions or selectors, otherwise you are going to have a bad time. Luckily you don't really have to worry about it. Clang has an exception for class objects that states

"As a special case, if the object’s base type is Class (possibly protocol-qualified), the type is adjusted to have __unsafe_unretained qualification instead."

Yay, Clang saves the day! Unfortunately this provision only exists when the type is Class. If there is no qualification whatsoever, the infamous id, then you are playing with fire since anyone could try to retain it.

The real issue here is that root classes that don't conform to the NSObject protocol are a real danger. Inserting an id into an array is only safe it if is id<NSObject>. Basically every method that takes an object really expects conformance to that protocol. Custom root classes are kind of nasty!

But why on Earth are you making root classes or using objects that don't implement -retain and -release? Stop it, or at least make it conform to the NSObject protocol.

Ah, the world is safe again.