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

时间:2015-07-25 12:47:16

标签: xcode swift cocoa

我是一位经验丰富的程序员,但不熟悉Mac开发(在XCode和Swift中)。进入我的第一个'hello world'程序,我发现我认为是XCode / Swift中一个可怕的错误。但也许是我的误解。一些新用户不会期望的行为,这使得很容易意外地编写不安全的代码。

基本上,XCode认为属性存在时实际上并不存在。

下面的小应用程序演示了这一点(还有一个简单的GUI,其中包含一个文本框和一个链接到inputBox和clickedButton的按钮)。它报告没有错误,XCode很乐意编译并运行它。但是当调用clickedButton动作时,它会崩溃说:" [NSTextField placeholderString]:无法识别的选择器"

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
    @IBOutlet weak var inputBox: NSTextField!

    @IBAction func clickedButton(sender:AnyObject) {

        let placeholder : String = inputBox.placeholderString!
        let name : String = (inputBox.stringValue == "") ? placeholder : inputBox.stringValue
        println("Hi: \(name)!")

    }

    func applicationDidFinishLaunching(aNotification: NSNotification) {
    }

    func applicationWillTerminate(aNotification: NSNotification) {
    }

}

我发现NSTextField没有属性placeholderString,实际上placeholderString是相关NSTextFieldCell对象的属性,您可以使用cell()方法。以下行确实按预期工作:

let placeholder : String = (inputBox.cell()!).placeholderString!!

所以,问题是:

1)为什么XCode的自动完成功能会建议' placeholderString'在键入' inputBox。'之后,当输入框是NSTextField对象并且没有placeholderString属性时,将其作为一个可能的选项。

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

这只是XCode中的一个错误(我在小牛队使用的是6.2(6C131e)版本)?或者是否有更深层次的东西我对Swift和Objective-C与Cocoa框架之间的交互有所了解?

更新 Thomas Kilian给了我一个有用的提示 - 按住ctrl点击查看定义。所以我按住了CTR占位符属性并看到了这个:

@availability(OSX, introduced=10.10)
var placeholderString: String?

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

进一步更新

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

所以,虽然这是预期的记录行为,而不是“错误”,但它仍然意味着编写代码非常容易,编译得很好,然后因运行时错误而崩溃目标设备。 XCode积极推荐不起作用的方法。确保您的代码不会崩溃的唯一方法是查找您在文档中使用的每个方法并自行检查。 gnasher729建议使用较旧的SDK,但Apple没有提供任何降级方法。我个人认为这是不可接受的,特别是考虑到安全性是Swift的主要卖点之一。

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

所以我上面的例子可以安全地写成:

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仍处于测试阶段。有关他们博客的更多信息:https://developer.apple.com/swift/blog/?id=29

1 个答案:

答案 0 :(得分:3)

在每个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运行良好。

设置背后的想法是,它允许您使用在新操作系统版本上运行的新功能,同时能够在旧操作系统版本上运行,假设您小心避免新呼叫。

如果这对你来说太复杂了,要么使用10.9 SDK(对你来说没有10.10通话),要么只在10.10上部署(你的应用程序不会在10.9上运行)。