为什么XCode建议(并编译)不存在的对象属性?

为什么XCode建议(并编译)不存在的对象属性?,xcode,swift,cocoa,Xcode,Swift,Cocoa,我是一名经验丰富的程序员,但对Mac开发(XCode和Swift)还不熟悉。在我的第一个“hello world”程序开始10分钟后,我在XCode/Swift中发现了一个我认为可怕的bug。但也许是我的误解。一些新用户无法预料的行为,使得意外编写不安全代码变得非常容易 本质上,XCode认为属性存在,而实际上它不存在 下面的小应用程序演示了这一点(还有一个简单的GUI,带有一个文本框和一个链接到inputBox和clickedButton的按钮)。它不报告任何错误,XCode将很乐意编译并运行

我是一名经验丰富的程序员,但对Mac开发(XCode和Swift)还不熟悉。在我的第一个“hello world”程序开始10分钟后,我在XCode/Swift中发现了一个我认为可怕的bug。但也许是我的误解。一些新用户无法预料的行为,使得意外编写不安全代码变得非常容易

本质上,XCode认为属性存在,而实际上它不存在

下面的小应用程序演示了这一点(还有一个简单的GUI,带有一个文本框和一个链接到inputBox和clickedButton的按钮)。它不报告任何错误,XCode将很乐意编译并运行它。但是当调用clickedButton操作时,它会崩溃,并说:“[NSTextField Placeholder String]:无法识别的选择器”

后来我发现NSTextField没有属性Placeholder String,事实上Placeholder String是相关NSTextFieldCell对象的属性,您可以使用cell()方法获得该对象。下面这一行确实按预期工作:

let placeholder : String = (inputBox.cell()!).placeholderString!!
因此,问题是:

1) 为什么XCode的自动完成功能会在键入“inputBox.”后向我建议将“Placeholder String”作为一个可能的选项,而inputBox是一个NSTextField对象,并且没有Placeholder String属性

2) 这段代码为什么要编译?编译器肯定知道可以访问哪些属性并抛出错误

这只是XCode中的一个bug吗(我在Mavericks上使用的是6.2版(6C131e)?或者,关于Swift和Objective-C与Cocoa框架之间的相互作用,我还有什么更深层次的理解吗

更新 Thomas Kilian给了我一个有用的提示—按ctrl键查看定义。因此,我按住ctrl键单击了占位符属性,并看到以下内容:

@availability(OSX, introduced=10.10)
var placeholderString: String?
我的系统是10.9。所以我想这就解释了为什么我不能访问该属性。但是,我的应用程序的部署目标设置为OS X 10.9。所以XCode应该知道该属性不可用,并且不应该编译。对吗?XCode是否无法理解@availability属性

进一步更新

gnasher729在下面被接受的答案中非常清楚地解释了这种情况

因此,虽然这是预期的文档化行为,而不是“bug”,但这仍然意味着编写代码非常容易,这些代码可以很好地编译,然后在目标设备上由于运行时错误而崩溃。XCode主动建议不起作用的方法。确保您的代码不会崩溃的唯一方法似乎是在文档中查找您使用的每一种方法并自己检查。gnasher729建议只使用较旧的SDK,但苹果没有提供任何降级方法。我个人认为这是不可接受的,特别是考虑到安全是Swift的主要卖点之一

好消息是,XCode的人显然也这么认为,因为Swift 2(在XCode 7中)引入了自动可用性检查,它检查您使用的每种方法是否都能在目标平台上工作。它还允许您将代码放入
(如果可用)块中,以便在平台和版本上有条件地运行代码

因此,我上面的示例可以安全地写成:

var placeholder : String
if #available(OSX 10.10, *) {
    // use latest methods
    placeholder = inputBox.placeholderString!
} else {
    // otherwise fall back to old way
    placeholder = (inputBox.cell as! NSTextFieldCell).placeholderString!
}
XCode会自动检查,这很好


在撰写本文时,Swift2/XCode7仍处于测试阶段。更多信息请访问他们的博客:

在每个iOS和MacOS X版本上,都有三个重要的版本号:您正在使用的SDK的版本号(您在编译器中使用的)、允许您的应用程序运行的最低iOS或MacOS X版本的版本号,以及您的代码正在运行的实际设备的版本号

SDK编号告诉编译器可以调用哪些操作系统方法。如果SDK是MacOS X 10.9,则无法从MacOS X 10.10调用方法,编译器不允许这样做

如果您的部署版本(允许应用程序运行的最低版本)是例如10.9,而SDK是10.10,那么在10.9上运行时,如果您尝试调用仅存在的方法,则这些方法将崩溃。您需要手动检查此项;Swift 2有一些方法可以让这变得非常简单

在这种情况下,该应用程序将不会在10.8上运行。它将在10.9上运行,但如果使用10.10方法,它将崩溃。它将在10点9分正常运行

安装背后的想法是,它允许您使用在新OS版本上运行的新功能,同时能够在旧OS版本上运行,前提是您小心避免新调用


如果这对你来说太复杂了,可以使用10.9 SDK(你永远不会调用10.10),或者只在10.10上部署(你的应用程序不会在10.9上运行)

在每个iOS和MacOS X版本上,都有三个重要的版本号:您正在使用的SDK的版本号(您在编译器中使用的)、允许您的应用程序运行的最低iOS或MacOS X版本的版本号,以及您的代码正在运行的实际设备的版本号

SDK编号告诉编译器可以调用哪些操作系统方法。如果SDK是MacOS X 10.9,则无法从MacOS X 10.10调用方法,编译器不允许这样做

如果您的部署版本(允许应用程序运行的最低版本)是例如10.9,而SDK是10.10,那么在10.9上运行时,如果您尝试调用仅存在的方法,则这些方法将崩溃。您需要手动检查此项;Swift 2有一些方法可以让这变得非常简单

在这种情况下,该应用程序将不会在10.8上运行。它将在10.9上运行,但如果使用10.10方法,它将崩溃。它将在10点9分正常运行

安装背后的想法是,它允许您使用在新OS版本上运行的新功能,同时能够在旧OS版本上运行,前提是您小心避免新调用


如果这对你来说太复杂了,可以使用10.9 SDK(你永远不会调用10.10),或者只在10.10上部署(你的应用程序不会在10.9上运行)

在每个iOS和MacOS X版本上都有
var placeholder : String
if #available(OSX 10.10, *) {
    // use latest methods
    placeholder = inputBox.placeholderString!
} else {
    // otherwise fall back to old way
    placeholder = (inputBox.cell as! NSTextFieldCell).placeholderString!
}