在Objective C中,为什么我可以在没有错误或警告的情况下将NSArray分配给NSMutableArray?

Jea*_*let 14 cocoa objective-c clang

我被一种奇怪的行为所扰乱,如下例所示:

NSMutableArray *a1 = [[NSMutableArray alloc] init]; // fine
NSMutableArray *a2 = [NSMutableArray array];        // fine, too

// compiler reports incompatible pointer types; good:
NSMutableArray *a3 = [[NSArray alloc] init]; 

// compiler says nothing and is happy to assign this!
NSMutableArray *a4 = [NSArray array]; 
Run Code Online (Sandbox Code Playgroud)

这两个initarray的方法,无论是NSArrayNSMutableArray类返回id.但是,当我调用这些方法时的行为简直不一样,而clang让我愉快地NSArrayNSMutableArray变量分配一个空!

事实证明,clang会自动更改某些方法的返回类型,包括initfamily,toinstancetype,从而能够在编译时确定[[NSArray alloc] init]返回an NSArray *而不是an NSMutableArray *.但是这种检查根本不适用于该array方法.

为什么?不应该像我上一个例子那样的行至少产生警告吗?为什么不将所有这些方法声明为返回instancetype?它将来会改变吗?


更新

好消息:从iOS 7开始,[NSArray array]返回instancetype,因此a4上面的分配也会产生警告.其他方法,如arrayWithContentsOfFile:arrayWithContentsOfURL仍然返回id,但......

jus*_*tin 10

但是这个检查根本不适用于数组方法.为什么?

正如您链接的文档所描述的那样,这是因为-array不会产生可识别的相关结果类型.ObjC非常动态 - 编译器无法保证结果类型+array.它作出这样的假设有一些方法,因为命名约定是明确的(例如+alloc,-init,+new,-self,等).所以这个实现只是采用命名约定.

编译器还验证了您可能不期望的区域中的一些命名约定:

@implementation NSArray (DEMO)

- (id)initStr
{
    return [NSString new]; // << warning. RE: init prefix
}

@end
Run Code Online (Sandbox Code Playgroud)

不应该像我上一个例子那样的行至少产生警告吗?为什么不将所有这些方法声明为返回实例类型?它将来会改变吗?

instancetype是大约一年前推出的(从它的外观).一些API是几十年前写的.我怀疑它会及时发生 - 因为(如果使用得当)它可以指出现有代码中的很多问题.当然,这些变化会破坏现有的构建(再次,如果在正确的位置声明,通常会进行良好的修正).

因此,文件错误并提供工具和库几年更新.假设进行了更改,则可能会在主要操作系统更新时发生.

如果它在一段时间内作为可选警告启用(在系统标题的情况下)可能是最好的.当然,他们仍然可以使用它与旧API编译器的向后兼容性.

此外,这种变化可以很容易地改装(不是早期编译器将使之间的语义差异感id,并instancetype通过简单的typedef).typedef的一个问题是它是一个全局声明 - 编译器可以将word/modifier /属性限制到给定的范围,而不会通过添加全局typedef来模拟关键字.Apple的GCC可能永远不会支持instancetype,因此为Apple的GCC引入它的合理方式可能是全局typedefid,这可能会给某些人带来问题(如果采用这种方式,则没有语义上的好处).请注意,Apple过去也做过类似的重大改变.