In my previous entry, I discussed when id
will be promoted to instancetype
. But now that I’ve explained this, I’d like to explain why you should understand this but not rely on it. Instead, you should use instancetype
directly.
Let me start with this bold statement, then I’ll back up and explain it: Use instancetype
whenever it’s appropriate, which is whenever a class returns an instance of that same class.
First, some definitions:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // convenience constructor
@end
For a convenience constructor, you should always use instancetype
. The compiler does not automatically convert id
to instancetype
.
For initializer, it’s more complicated. When you type this:
- (id)initWithBar:(NSInteger)bar
…the compiler will pretend you typed this instead:
- (instancetype)initWithBar:(NSInteger)bar
This was necessary for ARC. This is why people will tell you it isn’t necessary to use instancetype
, though I contend you should. The rest of this answer deals with this.
There’s three advantages:
- Explicit. Your code is doing what it says, rather than something else.
- Pattern. You’re building good habits for times it does matter, which do exist.
- Consistency. You’ve established some consistency to your code, which makes it more readable.
Explicit
It’s true that there’s no technical benefit to returning instancetype
from an init
. But this is because the compiler automatically converts the id
to instancetype
. You are relying on this quirk; while you’re writing that the init
returns an id
, the compiler is interpreting it as if it returns an instancetype
.
These are equivalent to the compiler:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
These are not equivalent to your eyes. At best, you will learn to ignore the difference and skim over it. This is not something you should learn to ignore.
Pattern
While there’s no difference with init
and other methods, there is a different as soon as you define a convenience constructor.
These two are not equivalent:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
You want the second form. If you are used to typing instancetype
as the return type of a constructor, you’ll get it right every time.
Consistency
Finally, imagine if you put it all together: you want an init
function and also a convenience constructor.
If you use id
for init
, you end up with code like this:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
But if you use instancetype
, you get this:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
It’s more consistent and more readable. They return the same thing, and now that’s obvious.
Conclusion
Unless you’re intentionally writing code for old compilers, you should use instancetype
when appropriate.
You should hesitate before writing a message that returns id
. Ask yourself: Is this returning an instance of this class? If so, it’s an instancetype
.
There are certainly cases where you need to return id
; namely, if you’re returning a different class. But you’ll probably use instancetype
much more frequently than id
.