我正在按照本教程学习SwiftiOS8/OSX 10.10,并且多次使用术语“未包装的值”,如本段(在对象和类下):
使用可选值时,可以在方法、属性和下标等操作前写?。如果?之前的值为nil,则忽略?之后的所有内容,整个表达式的值为nil。否则,可选值被解包,?之后的所有内容都作用于解包的值。在这两种情况下,整个表达式的值都是可选值。
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
我不明白,在网上搜索没有运气。
这是什么意思?
从Cezary的回答来看,原始代码的输出和最终解决方案(在操场上测试)之间略有不同:
在第二种情况下,超类的属性显示在输出中,而在第一种情况下有一个空对象。
两种情况下的结果不应该是相同的吗?
相关Q
首先,你必须了解什么是可选类型。可选类型基本上意味着变量可以是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")
}
现有的正确答案是伟大的,但我发现,对我来说,要完全理解这一点,我需要一个很好的类比,因为这是一个非常抽象和奇怪的概念。
所以,让我通过给出正确答案之外的不同观点来帮助那些“右脑”(视觉思维)开发人员。这是一个很好的类比,对我帮助很大。
生日礼物包装类比
把可选选项想象成生日礼物,用坚硬、坚硬、彩色的包装。
直到你打开礼物,你才知道包装里有没有东西——也许里面什么都没有!如果里面有东西,那可能是另一个礼物,它也被包裹了,也可能什么都没有。你甚至可能打开100个嵌套的礼物,最终发现除了包装什么都没有。
如果可选的值不是nil
,现在您已经显示了一个包含某些内容的框。但是,特别是如果该值没有显式键入并且是一个变量而不是预定义的常量,那么您可能仍然需要打开该框,然后才能知道该框中的任何具体内容,例如它是什么类型,或者实际值是什么。
-盒子里是什么-类比
即使在你打开变量之后,你仍然像SE7EN中最后一个场景中的布拉德·皮特(警告:剧透和非常R级的脏话和暴力),因为即使在你打开了现在之后,你也处于以下情况:你现在有nil
,或者一个包含某些东西的盒子(但你不知道是什么)。
你可能知道某个东西的类型。例如,如果你将变量声明为类型,[Int: Any?]
,那么你就会知道你有一个(可能为空的)带有整数下标的字典,它会产生任何旧类型的包装内容。
这就是为什么在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)
Swift非常重视类型安全。整个Swift语言的设计都考虑到了安全性。这是Swift的标志之一,也是您应该张开双臂欢迎的标志。它将有助于开发干净、可读的代码,并有助于防止您的应用程序崩溃。
Swift中的所有选项都使用?
符号来划分。通过将?
设置在您声明为可选类型的类型名称之后,您实际上不是将其转换为?
之前的类型,而是将其转换为可选类型。
注意:变量或类型Int
与Int?
不同。它们是两种不同的类型,不能相互操作。
使用可选
var myString: String?
myString = "foobar"
这并不意味着您正在使用一种类型的String
。这意味着您正在使用一种类型的String?
(字符串可选,或可选字符串)。事实上,每当您尝试
print(myString)
在运行时,调试控制台将打印可选项("foobar")
。"可选项()
"部分表示该变量在运行时可能有值,也可能没有值,但恰好当前包含字符串"fobar"。除非您执行所谓的“解包装”可选值,否则此“可选项()
”指示将保留。
打开可选类型意味着您现在将该类型转换为非可选类型。这将生成一个新类型,并将驻留在该可选类型中的值分配给新的非可选类型。这样,您就可以对该变量执行操作,因为编译器已保证它具有实值。
有条件地展开将检查可选中的值是否为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")
}
强制展开是通过使用所谓的!
("ang")运算符来完成的。这不太安全,但仍允许您的代码编译。然而,无论何时使用ang运算符,在强制展开之前,您必须1000%确定您的变量确实包含一个实值。
var myString: String?
myString = "foobar"
print(myString!)
上面的代码是完全有效的Swift代码。它打印出设置为“foobar”的myString
的值。用户会看到foobar
打印在控制台中,仅此而已。但让我们假设从未设置过该值:
var myString: String?
print(myString!)
现在我们面临着不同的情况。与Objective-C不同,每当尝试强制打开一个可选选项,并且该可选选项尚未设置并且为nil
时,当您尝试打开该可选选项以查看应用程序内部的内容时将崩溃。
带类型转换的解包装。正如我们之前所说,当您解包装
可选类型时,您实际上是在转换为非可选类型,您也可以将非可选类型转换为不同的类型。例如:
var something: Any?
在我们的代码中,变量的某个地方
将被设置为某个值。也许我们正在使用泛型,或者可能有其他一些逻辑正在发生,这将导致这种情况发生变化。所以在我们的代码后面,我们想使用的东西
,但如果它是不同的类型,仍然能够以不同的方式对待它。在这种情况下,您将希望使用作为
关键字来确定这一点:
注意:as
运算符是您在Swift中键入cast的方式。
// 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
请注意两个作为
关键字之间的区别。就像以前我们强行打开一个可选的时一样,我们使用!
ang运算符来执行此操作。在这里,您将执行相同的操作,但不是将其转换为非可选的,而是将其转换为Int
。并且它必须能够向下转换为Int
,否则,就像在值为nil
时使用ang运算符一样,您的应用程序将崩溃。
为了在某种排序或数学运算中使用这些变量,它们必须被打开才能这样做。
例如,在Swift中,只有相同类型的有效数字数据类型可以相互操作。当您将转换为!
的类型时,您将强制对该变量进行向下转换,就好像您确定它是该类型一样,因此操作起来是安全的,不会使您的应用程序崩溃。只要变量确实是您要转换的类型,这是可以的,否则您的手会一团糟。
尽管如此,使用as!
进行转换将允许您的代码进行编译。使用as?
进行转换是另一回事。事实上,as?
将您的Int
声明为完全不同的数据类型。
现在它是可选的(0)
如果你曾经试着写一些像这样的东西
1 + Optional(1) = 2
你的数学老师可能会给你一个“F”。Swift也是如此。除了Swift宁愿根本不编译也不愿给你打分。因为最终可选的可能实际上是零。
安全第一的孩子。