在Swift中什么是“未包装的值”?


155

通过遵循本教程,我正在学习iOS 8 / OSX 10.10的Swift ,并且多次使用术语“ 未包装的值 ”,如本段中(在Objects和Class下):

使用可选值时,您可以编写?在操作之前,例如方法,属性和下标。如果前值?是零,之后的所有内容?将被忽略,整个表达式的值为nil。否则,将取消包装可选值,以及?之后的所有内容。作用于未包装的值。在这两种情况下,整个表达式的值都是一个可选值。

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

我不明白,在网上没有运气。

这是什么意思?


编辑

根据Cezary的回答,原始代码的输出与最终解决方案(在操场上测试)的输出之间存在细微差别:

原始码

原始码

塞扎里的解决方案

塞扎里的解决方案

在第二种情况下,超类的属性显示在输出中,而在第一种情况下,则显示一个空对象。

两种情况下的结果不应该相同吗?

相关问答:Swift中的可选值是什么?


1
塞扎里的答案是正确的。另外,iBooks上
Storm

@DanielStormApps这本iBook是我所提问的链接教程的便携式版本:)
Bigood 2014年

Answers:


289

首先,您必须了解什么是Optional类型。可选类型基本上意味着变量可以是 nil

例:

var canBeNil : Int? = 4
canBeNil = nil

问号表明一个事实,即canBeNil可以nil

这行不通:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

要从变量中获取值(如果该值是可选的),则必须将其解包。这仅意味着在结尾处添加一个感叹号。

var canBeNil : Int? = 4
println(canBeNil!)

您的代码应如下所示:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

旁注:

您还可以声明可选内容,以使用感叹号而不是问号来自动展开。

例:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

因此,修复代码的另一种方法是:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

编辑:

您所看到的差异恰好是可选值被包装的症状。在其之上还有另一层。在展开的版本只是表明直对象,因为它是很好,解开。

快速游乐场比较:

操场

在第一种和第二种情况下,对象不会自动解包,因此您会看到两个“图层”({{...}}),而在第三种情况下,您会看到仅一层({...}),因为对象会自动解包。

第一种情况和第二种情况的区别在于,如果optionalSquare将设置为,则后两种情况会给您带来运行时错误nil。在第一种情况下使用语法,您可以执行以下操作:

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}

1
感谢您的明确解释!我的问题中包含的代码引用自Apple的教程(问题中的链接),我想它应该是按原样工作的(确实如此)。不过,您的最终解决方案和原始解决方案会输出不同的结果(请参见我的编辑)。任何想法?
Bigood 2014年

2
凉。很好的解释。我还是很困惑。为什么要使用可选值?什么时候有用?苹果为什么要创建这种行为和语法?
托德·雷曼

1
对于类属性,它尤其重要。Swift要求init()在类的函数中初始化所有非可选方法。我建议阅读Apple发行的Swift书中的“基础知识”(包括可选内容),“初始化”和“可选链接”章节。
Cezary Wojcik 2014年

2
@ToddLehman苹果公司创建了此文件,以帮助您编写更安全的代码。例如,想象一下Java中所有的null指针异常都会使程序崩溃,因为有人忘记检查null。还是在Objective-C上有奇怪的行为,因为您忘记检查nil。使用Optional类型,您不能忘记处理nil。编译器会强制您处理它。
Erik Engheim 2014年

5
@AdamSmith —来自Java背景,我绝对可以看到可选控件的使用...但是(最近)来自Objective-C背景,我仍然很难看到它的使用,因为Objective- C明确允许您将消息发送到nil个对象。嗯 我很乐意看到有人写下这些示例,以显示与不使用它们的等效代码相比,它们如何使代码更短,更安全。我并不是说它们没有用;我只是说我还没有“明白”。
Todd Lehman 2014年

17

现有的正确答案很好,但是我发现对于我来说,要完全理解这一点,我需要一个很好的类比,因为这是一个非常抽象和怪异的概念。

因此,让我通过提供正确答案之外的其他观点来帮助那些“头脑清晰”(视觉思维)开发人员。这是一个很好的类比,对我有很大帮助。

生日礼物包装类比

可以将可选商品视为生日礼物,以硬,硬,彩色的包装包裹。

在打开礼物之前,不知道包裹内是否有东西-也许里面什么都没有!如果里面有东西,那可能是另一个礼物,也被包裹了,也可能什么没有。您甚至可以拆开100个嵌套的礼物,最后发现除了包裹外别无其他

如果option的值不是nil,那么您已经显示了一个包含某些内容的框。但是,尤其是如果该值未明确键入并且是变量而不是预定义的常量,那么您可能仍需要打开框,然后才能知道框中内容的具体信息,例如其类型类型。实际是。

盒子里装了什么?!比喻

即使在解开变量之后,您仍然像SE7EN最后一幕中的布拉德·皮特(Brad Pitt)(警告:破坏者和非常受R级批评的语言和暴力),因为即使在解开当前之后,您仍处于以下情况:您现在有了nil,或一个装有某些物品的盒子(但您不知道什么)。

您可能知道某物的类型。例如,如果您将变量声明为类型,[Int:Any?]那么您将知道您有一个(可能为空)Dictionary,该字典具有整数下标,该整数下标会产生任何旧类型的包装内容。

这就是为什么在Swift中处理集合类型(字典和数组)会有点麻烦。

例子:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)

不错
SamSol

喜欢这个比喻!那么,有没有更好的方法来打开盒子呢?在您的代码示例中
David T.

薛定
ding

感谢您让我感到困惑....什么样的程序员提供的生日礼物总是空的?有点意思
delta2flat '18

@Jacksonkr还是不行。
amsmath

13

Swift对类型安全性给予很高的重视。整个Swift语言在设计时都考虑了安全性。它是Swift的标志之一,您应该张开双臂欢迎它。它将有助于开发清晰易读的代码,并有助于防止应用程序崩溃。

Swift中的所有可选选项均以?符号分隔。通过?在您声明为可选的类型的名称之后设置after,您实际上将其强制转换为不是在之前的类型?,而是强制可选类型。


注意:变量或类型Int一样的Int?。它们是两种不能相互操作的不同类型。


使用可选

var myString: String?

myString = "foobar"

但这并不意味着你是一个类型的工作String。这意味着您正在使用String?(字符串可选,或可选字符串)类型。实际上,每当您尝试

print(myString)

在运行时,调试控制台将打印Optional("foobar")。“ Optional()”部分表示此变量在运行时可能有值,也可能没有值,但是正好当前包含字符串“ foobar”。这个 ”Optional()除非您执行所谓的“解包”可选值,否则指示将保持不变。

展开可选内容意味着您现在正在将该类型转换为非可选内容。这将生成一个新类型,并将驻留在该可选值内的值分配给新的非可选类型。这样,您可以对该变量执行操作,因为编译器已保证该变量具有固定值。


有条件地展开将检查可选中的值是否为nil。如果不是nil,则将有一个新创建的常量变量,该常量变量将被分配该值并将其展开为非可选常量。从那里,您可以安全地在if块中使用非可选选项。

注意:您可以为有条件解包的常量赋予与要解包的可选变量相同的名称。

if let myString = myString {
    print(myString)
    // will print "foobar"
}

有条件地展开可选项是访问可选项的值的最干净的方法,因为如果它包含一个nil值,则if let块中的所有内容都将不会执行。当然,像任何if语句一样,您可以包含else块

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

强制展开是通过使用所谓的!(“ bang”)运算符完成的。这不太安全,但仍允许您的代码进行编译。但是,无论何时使用bang运算符,都必须确保在强制展开之前,您的变量确实包含一个实数值,这必须是1000%。

var myString: String?

myString = "foobar"

print(myString!)

以上是完全有效的Swift代码。它打印出myString设置为“ foobar”的值。用户将foobar在控制台中看到打印内容,仅此而已。但让我们假设该值从未设置过:

var myString: String?

print(myString!) 

现在,我们面临着另一种情况。与Objective-C不同,每当尝试强行打开可选对象,而未设置nil可选对象为时,当您尝试打开可选对象以查看应用程序内部崩溃时,该对象将崩溃。


带类型转换的展开。如前所述,虽然您是unwrapping可选的,但实际上是将其转换为非可选类型,也可以将非可选类型转换为其他类型。例如:

var something: Any?

在我们代码的某个位置,变量something将被设置一些值。也许我们正在使用泛型,或者可能正在发生其他逻辑,这将导致这种情况发生改变。因此,在我们的代码后面,我们想使用,something但是如果它是不同的类型,仍然可以区别对待。在这种情况下,您将需要使用as关键字来确定这一点:

注意:as运算符是您在Swift中键入强制类型转换的方式。

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

注意两个as关键字之间的区别。像以前一样,当我们强行打开一个可选的包装时,我们使用了!bang运算符进行了包装。在这里,您将执行相同的操作,但不是将其强制转换为非可选,而是将其强制转换为Int。并且必须能够向下转换为Int,否则就像在值是时使用bang运算符一样nil您的应用程序会崩溃。

为了在某​​种排序或数学运算中完全使用这些变量,必须对其进行包装。

例如,在Swift中,只能对相同类型的有效数字数据类型进行互操作。当您使用as!强制转换类型时,您将强制对该变量进行向下转换,就好像您确定它属于该类型一样,因此可以安全地进行操作并且不会使应用程序崩溃。只要变量确实是您要对其强制转换的类型,就可以,否则,您将一团糟。

尽管如此,使用强制类型转换as!将允许您的代码进行编译。用铸造as?是一个不同的故事。实际上,as?将您Int一起声明为完全不同的数据类型。

现在它是 Optional(0)

而且,如果您尝试写作业,例如

1 + Optional(1) = 2

您的数学老师可能会给您一个“ F”。与Swift相同。除了Swift,她宁愿根本不编译,也不愿给你一个分数。因为在一天结束时,可选项实际上可能为零

安全第一的孩子。


很好的解释了可选类型。帮助我清理了一切。
Tobe_Sta

0

'?' 表示可选的链接表达式

,“!”表示力值
在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.