Clang添加了一个关键字instancetype,据我所知,它将id替换为-alloc和init中的返回类型。
使用instancetype而不是id是否有好处?
Clang添加了一个关键字instancetype,据我所知,它将id替换为-alloc和init中的返回类型。
使用instancetype而不是id是否有好处?
这肯定是有好处的。当你使用'id'时,你基本上不会得到任何类型检查。使用instancetype,编译器和IDE知道返回的是什么类型的东西,可以更好地检查代码并更好地自动完成。
当然,只在有意义的地方使用它(即返回该类实例的方法);Id仍然有用。
是的,在所有应用instancetype的情况下使用它都有好处。我将更详细地解释,但让我从这句粗体字开始:在适当的时候使用instancetype,即每当一个类返回同一类的实例时。
事实上,苹果在这个问题上是这样说的:
在代码中,在适当的地方用instancetype替换出现的id作为返回值。这是init方法和类工厂方法的典型情况。即使编译器自动转换以“alloc”、“init”或“new”开头且返回类型为id的方法以返回instancetype,它也不会转换其他方法。Objective-C的约定是为所有方法显式地编写instancetype。
我特别强调。来源:采用现代Objective-C
有了这些,让我们继续解释为什么这是一个好主意。
首先,一些定义:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
对于类工厂,您应该始终使用instancetype。编译器不会自动将id转换为instancetype。id是一个泛型对象。但如果你把它设置为实例类型,编译器就知道方法返回的对象类型。
这不是学术问题。例如,[[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]将在Mac OS X上生成一个错误(仅)多个名为'writeData:'的方法,发现不匹配的结果,参数类型或属性。原因是NSFileHandle和NSURLHandle都提供了一个写数据:。由于[NSFileHandle fileHandleWithStandardOutput]返回一个id,编译器不确定是什么类writeData:被调用。
你需要解决这个问题,使用:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
or:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
当然,更好的解决方案是将fileHandleWithStandardOutput声明为返回实例类型。那么演员或分配就没有必要了。
(注意,在iOS上,这个例子不会产生一个错误,因为只有NSFileHandle提供了一个writeData: there。其他的例子也存在,比如length,它从UILayoutSupport返回CGFloat,但从NSString返回NSUInteger。)
注意:自从我写了这个,macOS头已经被修改为返回一个NSFileHandle而不是一个id。
对于初始化器,它更复杂。当你输入这个:
- (id)initWithBar:(NSInteger)bar
编译器会假装你输入了这个:
- (instancetype)initWithBar:(NSInteger)bar
这对于ARC来说是必要的。这在Clang Language Extensions Related results types中有描述。这就是为什么人们会告诉你没有必要使用instancetype,尽管我认为你应该使用。剩下的答案是关于这个的。
它有三个优点:
明确。你的代码是按照它说的去做,而不是别的什么。 模式。你正在为重要的时刻培养好习惯,这些时刻确实存在。 一致性。您已经为您的代码建立了一些一致性,这使其更具可读性。
显式的
的确,从init返回instancetype没有技术上的好处。但这是因为编译器会自动将id转换为instancetype。你依赖的是这个怪癖;当你写init返回一个id时,编译器会把它解释为返回一个实例类型。
这些等价于编译器:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
这些并不等同于你的眼睛。在最好的情况下,你将学会忽略其中的差异并略去它。这不是你应该学会忽视的东西。
模式
虽然init和其他方法没有区别,但一旦定义了类工厂,就有区别了。
这两者是不相等的:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
你想要第二种形式。如果您习惯于输入instancetype作为构造函数的返回类型,那么您每次都会正确地使用它。
一致性
最后,想象一下如果你把它们放在一起:你想要一个init函数和一个类工厂。
如果你在init中使用id,你最终会得到这样的代码:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
但是如果你使用instancetype,你会得到这个:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
它更一致,更可读。它们返回相同的东西,这很明显。
结论
除非您有意为旧的编译器编写代码,否则您应该在适当的时候使用instancetype。
在编写返回id的消息之前,您应该三思。问问自己:这返回的是该类的实例吗?如果是,它就是一个实例类型。
当然在某些情况下您需要返回id,但您可能会更频繁地使用instancetype。
以上答案足以解释这个问题。我只是想再加一个例子,让读者从编码的角度来理解它。
A类
@interface ClassA : NSObject
- (id)methodA;
- (instancetype)methodB;
@end
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
}
您还可以在指定初始化器中获得详细信息
**
INSTANCETYPE
** 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. **
ID
** 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 the init method will be the same class as the type of object it is initializing (that is, the receiver of the init 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 the init message to a newly alloc’ed Fraction object, the compiler will infer that the value returned from that init method (whose return type has been declared as type instancetype) will be a Fraction object. In the past the return type from an initialization method was declared as type id. 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.
初始化对象,Stephen G. Kochan, Objective-C编程,第6版