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 with `instancetype` where appropriate. This is typically the case for `init` methods and class factory methods. Even though the compiler automatically converts methods that begin with “alloc,” “init,” or “new” and have a return type of `id` to return `instancetype`, it doesn’t convert other methods. **Objective-C convention is to write `instancetype` explicitly for all methods.**
* Emphasis mine. Source: [Adopting Modern Objective-C](https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html#//apple_ref/doc/uid/TP40014150-CH1-SW11)
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`.)
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][1]. 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:
1. **Explicit.** Your code is doing what it says, rather than something else.
2. **Pattern.** You're building good habits for times it does matter, which do exist.
3. **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 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.
## Consistency ##
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.
## 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`, but you'll probably use `instancetype` much more frequently.
(http://stackoverflow.com/)
No comments:
Post a Comment