【问题标题】:In Objective C, why am I allowed to assign an NSArray to an NSMutableArray without error or warning?在 Objective C 中,为什么我可以在没有错误或警告的情况下将 NSArray 分配给 NSMutableArray?
【发布时间】:2013-02-21 07:23:57
【问题描述】:

我对一种奇怪的行为感到不安,如下例所示:

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]; 

NSArrayNSMutableArray 类的 initarray 方法都返回 id。但是,当我调用这些方法时的行为根本就不一样,clang 让我很高兴地将一个空的NSArray 分配给一个NSMutableArray 变量!

事实证明clang will automatically change the return type of some methods, including the init family, to instancetype,因此能够在编译时确定[[NSArray alloc] init] 返回NSArray * 而不是NSMutableArray *。但是这个检查根本不适用于array 方法。

为什么?像我上一个示例这样的行不应该产生至少一个警告吗?为什么不是所有这些方法都声明为返回instancetype?以后会变吗?


更新

好消息:从 iOS 7 开始,[NSArray array] 返回 instancetype,因此上面对 a4 的赋值也会产生警告。 arrayWithContentsOfFile:arrayWithContentsOfURL 等其他方法仍然返回 id,不过……

【问题讨论】:

  • 我会在bugreport.apple.com 提交错误并在此处提及错误编号。
  • @ericgorr 问题是,这不是错误。
  • @Jean-PhilippePellet:对不起,我复制了你的问题...stackoverflow.com/questions/15249505/…
  • @AnoopVaidya 我想你误解了id 的用途。
  • 错误报告 rdar://13357852

标签: objective-c cocoa clang


【解决方案1】:

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

正如您链接的文档所述,这是因为-array 没有产生可识别的相关结果类型。 ObjC 是非常动态的——编译器不能保证+array 的结果类型。它确实使用某些方法做出了这种假设,因为命名约定定义明确(例如+alloc-init+new-self 等)。所以这个实现只是诉诸命名约定。

编译器还会验证您可能意想不到的一些命名约定:

@implementation NSArray (DEMO)

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

@end

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

instancetype 是大约一年前推出的(从外观上看)。一些 API 是几十年前编写的。我怀疑它会发生——及时——因为(如果使用正确)它可以指出现有代码中的很多问题。当然,这些更改会破坏现有的构建(同样,如果在正确的位置声明,通常是很好的更正)。

所以提交错误并给工具和库几年的时间来更新。假设进行了更改,它可能会在主要的操作系统更新时发生。

最好将它作为可选警告启用一段时间(在系统标头的情况下)。当然,他们仍然可以将它用于新 API 的旧编译器向后兼容。

此外,这种更改可以很容易地通过一个简单的 typedef 进行改进(并不是说早期的编译器会理解 idinstancetype 之间的语义差异)。 typedef 的一个问题是它是一个全局声明——编译器可以将单词/修饰符/属性限制在给定范围内,而不会通过添加全局 typedef 来模拟关键字的所有痛苦。 Apple 的 GCC 可能永远不会支持 instancetype,因此为 Apple 的 GCC 引入它的合乎逻辑的方式可能是 id 的全局 typedef,这可能会给某些人带来问题(如果采用该路线,则没有语义上的好处) .请注意,Apple 过去曾进行过类似的重大更改。

【讨论】:

    【解决方案2】:

    事实证明,您不仅可以使用错误的数组类型,还可以使用返回id 的便捷初始化程序使用错误类型的任何对象。例如,编译时没有看到警告:

    NSMutableArray *a4 = [NSDictionary dictionary]; 
    

    这是使用id 选择退出类型安全的副作用,正如您所注意到的,它应该被弃用并替换为 instancetype(以上述方式使用时会引发不兼容的类型警告)。

    很遗憾,这不是错误。 instancetype 是一个相当新的关键字,它的采用还没有普及,在 Apple 的框架中开始使用它是一个大胆的举措。你永远不知道,下一个 SDK 总是有希望的!

    【讨论】:

    • 谢谢。我一直想知道为什么这些方法首先应该返回 id 而不是它们返回的真实类型。为什么要像这样抛弃类型安全?
    • 所以它们不会破坏子类。如果您在初始化程序中放弃类型安全,则子类 S 可以覆盖 T 的 init 方法并返回自身而不会出现类型检查警告。如果 ObjC 就像 java 中的初始化程序是唯一的并且默认情况下不被继承,那么这将不是问题。
    • 嗯,我发现 not 返回 id 实际上 not 是个问题,因为 clang 允许您在一个被覆盖的方法。所以子类也应该没问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-16
    • 1970-01-01
    • 2020-10-23
    • 2015-04-01
    • 1970-01-01
    • 2017-07-25
    相关资源
    最近更新 更多