Clang adds a keyword instancetype
that, as far as I can see, replaces id
as a return type in -alloc
and init
.
Is there a benefit to using instancetype
instead of id
?
Clang adds a keyword instancetype
that, as far as I can see, replaces id
as a return type in -alloc
and init
.
Is there a benefit to using instancetype
instead of id
?
There definitely is a benefit. When you use 'id', you get essentially no type checking at all. With instancetype, the compiler and IDE know what type of thing is being returned, and can check your code better and autocomplete better.
Only use it where it makes sense of course (i.e. a method that is returning an instance of that class); id is still useful.
alloc
, init
, etc. are automatically promoted to instancetype
by the compiler. That isn't to say there's no benefit; there is, but it isn't this. –
Nigrosine [NSMutableArray array]
still return id
and not instancetype
? This allows weird stuff like in this question. –
Endor id
is automatically converted to instancetype
by the compiler. From the clang docs "To determine whether a method has an inferred related result type, the first word in the camel-case selector (e.g., “init” in “initWithObjects”) is considered…" –
Goldshlag Yes, there are benefits to using instancetype
in all cases where it applies. I'll explain in more detail, but let me start with this bold statement: Use instancetype
whenever it's appropriate, which is whenever a class returns an instance of that same class.
In fact, here's what Apple now says on the subject:
In your code, replace occurrences of
id
as a return value withinstancetype
where appropriate. This is typically the case forinit
methods and class factory methods. Even though the compiler automatically converts methods that begin with “alloc,” “init,” or “new” and have a return type ofid
to returninstancetype
, it doesn’t convert other methods. Objective-C convention is to writeinstancetype
explicitly for all methods.
With that out of the way, let's move on and explain why it's a good idea.
First, some definitions:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
For a class factory, you should always use instancetype
. The compiler does not automatically convert id
to instancetype
. That id
is a generic object. But if you make it an instancetype
the compiler knows what type of object the method returns.
This is not an academic problem. For instance, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
will generate an error on Mac OS X (only) Multiple methods named 'writeData:' found with mismatched result, parameter type or attributes. The reason is that both NSFileHandle and NSURLHandle provide a writeData:
. Since [NSFileHandle fileHandleWithStandardOutput]
returns an id
, the compiler is not certain what class writeData:
is being called on.
You need to work around this, using either:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
or:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Of course, the better solution is to declare fileHandleWithStandardOutput
as returning an instancetype
. Then the cast or assignment isn't necessary.
(Note that on iOS, this example won't produce an error as only NSFileHandle
provides a writeData:
there. Other examples exist, such as length
, which returns a CGFloat
from UILayoutSupport
but a NSUInteger
from NSString
.)
Note: Since I wrote this, the macOS headers have been modified to return a NSFileHandle
instead of an id
.
For initializers, 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 described in Clang Language Extensions Related result types. 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:
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.
While there's no difference with init
and other methods, there is a difference as soon as you define a class factory.
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.
Finally, imagine if you put it all together: you want an init
function and also a class factory.
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.
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
, but you'll probably use instancetype
much more frequently.
id
, you do indeed get type checking, but only in certain circumstances. –
Nigrosine [NSArray arrayWithObjects: ...]
should (and will?) return instancetype
? –
Endor id
as instancetype
when certain typical conditions are met. convenient, but it makes it unlikely that Apple will change the foundation classes. –
Cyclostome id
or instancetype
on init
methods is a style decision. There are reasons for both approaches, depending on your coding style one or the other may be more suited. I think that the reasons for using id
outweigh those for using instancetype
on init
methods. –
Hispanicize instancetype
vs id
really isn't a style decision. The recent changes around instancetype
really make it clear that we should use instancetype
in places like -init
where we mean 'an instance of my class' –
Cyclostome - (id)init...
methods have returned instances of the respective class in Objective-C for more than 20 years now, there is no clarification needed. If you try to return something else you get a compiler error anyway now. That said, I totally respect your choice of another style, and there are both great programmers out there that use the style you prefer, and that use the style I prefer. Peace? –
Hispanicize - (id)init
methods in Objective-C return an instance of the respective class is in place for decades now. Suddenly using a new keyword to make that more expressive is more confusing than helpful for most, because most do not realize that it does not make any difference to the compiler, and adds no safety at all. You may consider it expressive, I consider it as writing a longer term for something that a user of the language has to learn anyway to understand all init
methods that were written before instancetype
existed as a keyword. –
Hispanicize init
methods return instances of the respective type. This is common knowledge that's now even enforced by the compiler! instancetype
is a recent addition, I think the trend to convert lots of init
methods to instancetype
started as earlier versions of the compiler weren't relating the result type, so that using instancetype
on init
methods actually made a technical difference. Even in iOS 7, lots of classes are still - (id)init
, new classes are using it at well. I won't think less of any programmer that uses either style. –
Hispanicize id
returned by init
is different than the id
returned by other methods is really easy, and getting easier every day. The mitigating circumstance is that returning id
from other types of methods is fairly uncommon. I don't think it's a good recommendation, that's all. :) –
Nigrosine instancetype
was, very often, not doing that because a class was accidentally hard coded. –
Cyclostome id
. The compiler infers and forgives but it does not force you to call [self class]
which would, in my opinion, be 'enforcement' of instancetype
. I am not even sure that it protects against hard coding the class with instancetype
, though I wish it did. –
Cyclostome A *a = [[B alloc] init]
you will get a compiler error, whether the init
's return type is declared as id
or instancetype
does not matter. I am totally for using instancetype
for convenience methods, that's why it has been introduced in the first place. –
Hispanicize [MSMutableString string]
on an SDK where it returned id
, you could assign it to A *a
. instancetype
is more a question of consistency and clarity of intent than a hard requirement. I maintain that you're better off learning and using it wherever it actually applies, rather than just where the compiler won't promote to it. (And, after all, those rules could change.) –
Nigrosine - (MyCoolClass *)initWithCoolness:(BOOL)affectCool
Wouldn't this solve the issue? I know it is not conventional, but is there some deeper reason why we don't do this? –
Romanic There definitely is a benefit. When you use 'id', you get essentially no type checking at all. With instancetype, the compiler and IDE know what type of thing is being returned, and can check your code better and autocomplete better.
Only use it where it makes sense of course (i.e. a method that is returning an instance of that class); id is still useful.
alloc
, init
, etc. are automatically promoted to instancetype
by the compiler. That isn't to say there's no benefit; there is, but it isn't this. –
Nigrosine [NSMutableArray array]
still return id
and not instancetype
? This allows weird stuff like in this question. –
Endor id
is automatically converted to instancetype
by the compiler. From the clang docs "To determine whether a method has an inferred related result type, the first word in the camel-case selector (e.g., “init” in “initWithObjects”) is considered…" –
Goldshlag Above answers are more than enough to explain this question. I would just like to add an example for the readers to understand it in terms of coding.
ClassA
@interface ClassA : NSObject
- (id)methodA;
- (instancetype)methodB;
@end
Class B
@interface ClassB : NSObject
- (id)methodX;
@end
TestViewController.m
#import "ClassA.h"
#import "ClassB.h"
- (void)viewDidLoad {
[[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime
[[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}
You also can get detail at The Designated Initializer
**
** This keyword can only be used for return type, that it matches with return type of receiver. init method always declared to return instancetype. Why not make the return type Party for party instance, for example? That would cause a problem if the Party class was ever subclassed. The subclass would inherit all of the methods from Party, including initializer and its return type. If an instance of the subclass was sent this initializer message, that would be return? Not a pointer to a Party instance, but a pointer to an instance of subclass. You might think that is No problem, I will override the initializer in the subclass to change the return type. But in Objective-C, you cannot have two methods with the same selector and different return types (or arguments). By specifying that an initialization method return "an instance of the receiving object," you would never have to worry what happens in this situation. **
** Before the instancetype has been introduced in Objective-C, initializers return id (eye-dee). This type is defined as "a pointer to any object". (id is a lot like void * in C.) As of this writing, XCode class templates still use id as the return type of initializers added in boilerplate code. Unlike instancetype, id can be used as more than just a return type. You can declare variables or method parameters of type id when you are unsure what type of object the variable will end up pointing to. You can use id when using fast enumeration to iterate over an array of multiple or unknow types of objects. Note that because id is undefined as "a pointer to any object," you do not include an * when declaring a variable or object parameter of this type.
The special type
instancetype
indicates that the return type from theinit
method will be the same class as the type of object it is initializing (that is, the receiver of theinit
message). This is an aid for the compiler so that it can check your program and flag potential type mismatches—it determines the class of the returned object based on context; that is, if you’re sending theinit
message to a newlyalloc
’edFraction
object, the compiler will infer that the value returned from thatinit
method (whose return type has been declared as type instancetype) will be aFraction
object. In the past the return type from an initialization method was declared as typeid
. This new type makes more sense when you consider subclassing, as the inherited initialization methods cannot explicitly define the type of object they will return.
Initializing Objects, Stephen G. Kochan, Programming in Objective-C, 6th Edition
© 2022 - 2024 — McMap. All rights reserved.
id
have been replaced withinstancetype
, eveninit
fromNSObject
. If you want to make you code compatible with swift you have to useinstancetype
– Mcglone