Why is instancetype used?
Asked Answered
B

3

25

Can someone please explain to me (in simple terms) why an instancetype is used in Objective-C?

- (instancetype) init { 
    self = [super init];
    if (self) { 
        // Custom initialization
    } 
    return self; 
} 
Bankston answered 15/3, 2016 at 19:35 Comment(0)
P
28

It's to increase type safety.

Back in the old days, initialisers just returned an object of type id (any object).

With normal initialisers (those that begin with "init", "alloc" or "new"), this wasn't usually a problem. The compiler would automatically infer the type that it returned and therefore restrict any method calls on the object to the instance methods of that class.

However, this was a problem with static convenience initialisers or "factory methods" that didn't necessarily follow the same naming convention - therefore it was unable to apply the same type safety.

This means that with a class like this:

@interface Foo : NSObject

+(id) aConvenienceInit;

@end

The compiler would accept code like this:

NSArray* subviews = [Foo aConvenienceInit].subviews;

Why? Because the returned object could be any object, so if you try and access a UIView property - there's no type safety to stop you.

However, now with instancetype, the result you get back is of type of your given instance. Now with this code:

@interface Foo : NSObject

+(instancetype) aConvenienceInit;

@end

...

NSArray* subviews = [Foo aConvenienceInit].subviews;

You'll get a compiler warning saying that the property subviews is not a member of Foo*:

Although it's worth noting that the compiler will automatically convert the return type from id to instancetype if your method begins with "alloc", "init" or "new" - but nonetheless using instancetype wherever you can is a good habit to get into.


See the Apple docs on instancetype for more info.

Procurable answered 15/3, 2016 at 19:51 Comment(5)
yes i was reminded of how it used to be id and then became instance type. I'm not sure if OP is actually asking for this though.Rothmuller
I believe that even before the introduction of instancetype keyword, the compiler had been actually checking the return type for initializers already.Fjeld
Not according to the Apple docs on it. Although I started coding in obj-c in the post ARC, post instancetype days, so I can't say for sure - but the point about type safety remains.Procurable
@originaluser2 For initializers. The problem were factory methods, e.g. [NSArray array] because the compiler could not detect they should be returning instancetype. However, init is easy to detect and the compiler was already doing that before.Fjeld
@Fjeld ah okay my bad, fixed example.Procurable
F
17

Imagine two classes:

@interface A : NSObject
- (instancetype)init;
@end

@interface B : A
@end

The init method from A is inherited to B. However, in both classes the method has a different return type. In A the return type is A and in B the return type is B.

There is no other way to declare the return type for initializers correctly. Note that most programming languages with classes don't even have return types for constructors, therefore they completely avoid the issue.

This is the reason why Obj-C needs instancetype but of course it can be used outside initializers, too.

Fjeld answered 15/3, 2016 at 19:41 Comment(1)
Could you expand the sample code above to include the initializer (in the implementation file(s)), as well as the constructor? I think it helps to have all three: interface, implementation and constructor.Correy
T
1

It is important to use instancetype instead of id in Objective-C if you are also using this code in Swift. Consider the following class declaration:

@interface MyObject : NSObject

+ (id)createMyObject;
- (void)f;

@end

If you want to create a MyObject instance in Swift 5.3 with createMyObject and then call f for this object, you will have to do the following:

let a = MyObject.createMyObject()
(a as? MyObject)?.f()

Now replace id with instancetype in MyObject to have the following Swift code:

let a = MyObject.create()
a?.f()

As you can see now, you can use MyObject.create() instead of MyObject.createMyObject(). And you don't need to use (a as? MyObject) since a is defined as MyObject? and not as Any.

Turoff answered 20/10, 2020 at 14:50 Comment(2)
Thanks for your suggestion, I did think about that approach but I preferred avoiding the extra iteration of the results in favor of speed even though it shouldn't really make a difference in speed. Note that the keypath approach requires Swift 5.2Marxmarxian
@LeoDabus I think you already updated the answer for Swift 5.2.Turoff

© 2022 - 2024 — McMap. All rights reserved.