Benedict's Soapbox

When and how to use instancetype

instancetype is a relatively new addition to Objective-C. It was introduced to improve type safety. Specifically, instancetype improves type safety for methods that return an instance of the type there were called on. Clang refers to these methods as having a “related result type”. init methods are the most common methods to have a related result type. Other methods with related return type are copy and class factory methods. Prior to the introduction of instancetype the compiler was able to infer the return type of init and copy thanks to a convention. Because the convention already addresses init and copy methods it’s class factory methods that are most improved by the introduction of instancetype.

Here’s 2 examples that illustrate the improved type safety provided by instancetype. The first example uses NSURLRequest which does not use instancetype for factory methods and the second example is NSString which does:

When to use instancetype examples

Declaring the return type of a method as instancetype instead of id allows the compiler to infer the return type for a class and its’ subclasses regardless of the method name.

So that’s the basics of instancetype. Now let’s look at when to use it, a gotcha when using it, and when not to use an explicit type or id.

When to use instancetype

Using instancetype forms implicit documentation that says “subclassing is allowed here”. If a class can be subclassed then we must write the methods to behave correctly when called on a subclass.

@implementation MyClass  
+(instancetype)buggyFactoryMethod  
{  
    return [[MyClass alloc] init]; //Wrong!!!  
}


+(instancetype)correctFactoryMethod  
{  
    return [[self alloc] init];  
}  
@end


@implementation MySubClass // this is a subclass of MyClass  
//We're not implementing any methods.  
@end  

When calling buggyFactoryMethod on MySubClass an instance of MyClass will be returned which is not what the API is promising. correctFactoryMethod does the right thing. It takes advantage of the fact that classes are objects and uses self in the class method to create an instance of the correct type. Subclasses will only need to override correctFactoryMethod if the subclass changes which method is the designated initialiser.

There’s not much need to write factory methods these days. Back in the pre-ARC world factory methods were much more useful as they reduced the possibility of accidental memory leaks. ARC has removed this potential pitfall and has thus removed much of the reason for adding factory methods to a class; it’s now just as easy to call init.

init method should also use instancetype instead of id. instancetype is more explicit and therefore better than id.

When to use an explicit type

Just as using instancetype is implicitly says “subclassing is cool”, we can use an explicit type to suggest that “subclassing is probably not wise”. A common usage for explicit types is singletons.

The singleton pattern restricts the instantiation of a class to one instance. In Objective-C the common apporach is to make the instance available with a class method. But what should the return type be:

+(instancetype)sharedInstance; //Wrong!  
+(MySingleton *)sharedInstance; //Correct  

Using instancetype as the return type is in conflict with the design of a singleton. The instancetype return type is saying “if you subclass this method then return an object of the type of the subclass.” If an sharedInstance is called on the base class then an instance of the base class will be created and return. If sharedInstance is then called on a subclass then it will either return the object created by the base class, which will be of the wrong type, or it will create another instance and violate the singleton pattern. Therefore singletons should not use instancetype, they should use an explicit type. (There are actually a few classes in Cocoa that make this mistake.)

Another pattern similar to the singleton pattern is the multiton pattern. The multiton patterns is when an key-value store is used to keep track of instances. NSValueTransformer uses the multiton pattern to store NSValueTransformer subclasses by name. This pattern presents a problem: what type should we use when retrieving instances? If we use NSValueTransformer then we will loose some type information and will have to cast the returned object. To avoid the cast we could use id as the return type. The downside to using id is that we lose all type information. We could use instancetype but that would result in a type mis-matches when retrieving an instance from calling the method on subclasses. A type mis-match is a lie and is therefore a bad thing that we should avoid. In this situation the best option is to use an explicit type. This means we get some type safety and we are confident that the type is also correct. We could also add a convenience method to subclasses to wrap the call and hide the ugly cast:

+(BECURLTransformer *)urlTransformer  
{  
    id urlTransformer = [NSValueTransformer valueTransformerForName:NSStringFromClass(BECURLTransformer)];  
    NSAssert([urlTransformer isKindOfClass:[BECURLTransformer class], @"Transformer for key BECURLTransformer is of the wrong type.");  
    return urlTransformer;  
}  

Incidentally, it’s worth noting that the compiler gives precedence to the init naming convention over the return type. For example, UIColor declares the return type of its init methods as UIColor (which I think is a bug in its self). Despite the explicit type the compiler still infers the return type of UIColor subclasses to be of the subclass type. I think this is incorrect. Using an explicit return type from an init method would be provide implicit hint that a class should not be subclassed.

When to use id

The final option is to use id as the return type. I’m struggling to think of any occasion when id is the correct choice. instancetype was introduced to remove the use cases where id was being used. Using instancetype or an explicit type are preferable as they let the compiler spot bugs for you.

For more details see: