【问题标题】:What is an "unwrapped value" in Swift?Swift 中的“未包装值”是什么?
【发布时间】:2014-07-24 22:17:51
【问题描述】:

我正在通过关注this tutorial 学习适用于 iOS 8 / OSX 10.10 的 Swift,并且多次使用术语“unwrapped value”,如本段所示(在 Objects 和类):

使用可选值时,您可以编写 ?手术前 比如方法、属性和下标。如果 ? 是零,之后的一切?被忽略,整体的价值 表达式为零。否则,解开可选值,并且 之后的一切?作用于展开的值。在这两种情况下, 整个表达式的值是一个可选值。

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

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

这是什么意思?


编辑

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

原码

Cezary 的解决方案

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

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

相关问答:What is an optional value in Swift?

【问题讨论】:

  • Cezary 的回答很到位。此外,FREE on iBooks 还提供了一本很棒的 Swift 介绍书
  • @DanielStormApps 这本 iBook 是我的问题中链接教程的便携版本 :)

标签: ios swift ios8


【解决方案1】:

首先,您必须了解什么是 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

编辑:

您看到的差异正是可选值被包装这一事实的症状。上面还有一层。 unwrapped 版本只显示直对象,因为它是展开的。

快速的游乐场比较:

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

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

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

【讨论】:

  • 感谢清晰的解释!我的问题中包含的代码引用自 Apple 的教程(问题中的链接),我想它应该按原样工作(确实如此)。不过,您的最终解决方案和原始解决方案输出不同的结果(请参阅我的编辑)。有什么想法吗?
  • 酷。很好的解释。不过,我仍然很困惑。为什么我要使用可选值?什么时候有用? Apple 为什么要创建这种行为和语法?
  • 在类属性方面尤其重要。 Swift 要求在类的init() 函数中初始化所有非可选方法。我建议阅读 Apple 发布的 Swift 书籍中的“基础”(涵盖可选)、“初始化”和“可选链”章节。
  • @ToddLehman Apple 创建此代码是为了帮助您编写更安全的代码。例如。想象一下 Java 中的所有空指针异常都会导致程序崩溃,因为有人忘记检查空值。或者在 Objective-C 上出现奇怪的行为,因为你忘了检查 nil。使用 Optional 类型,你不能忘记处理 nil。编译器会强制你处理它。
  • @AdamSmith — 来自 Java 背景,我绝对可以看到可选项的使用......但是(最近)来自 Objective-C 背景,我仍然无法看到使用它,因为 Objective-C 明确允许您向 nil 对象发送消息。唔。我很想看到有人写一个例子来展示它们如何帮助使代码比不使用它们的等效代码更短、更安全。我并不是说它们没有用。我只是说我还没有“明白”。
【解决方案2】:

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

所以,让我帮助那些“右脑”(视觉思维)开发人员,除了正确答案之外,还提供不同的观点。这是一个很好的类比,对我帮助很大。

生日礼物包装类比

将可选物品想象成生日礼物,采用坚硬、坚硬、彩色的包装。

在您打开礼物之前,您不知道包装内是否有任何东西——也许里面什么都没有!如果里面有东西,它可能是另一个礼物,它也被包裹着,而且可能什么都没有。您甚至可以打开 100 个嵌套的礼物,最终发现只有包装

如果可选的值不是nil,那么现在您已经显示了一个包含某物的框。但是,特别是如果该值没有显式键入并且是一个变量而不是预定义的常量,那么您可能仍然需要打开盒子,然后才能知道盒子中的任何具体内容,例如 什么类型,或者实际的是什么。

盒子里有什么?!类比

即使在你解包变量之后,你仍然像 last scene in 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)

【讨论】:

  • 不错 ? ?? ? ?? ? ?
  • 喜欢这个比喻!那么,有没有更好的方法来打开盒子呢?在您的代码示例中
  • 薛定谔的猫在盒子里
  • 感谢您让我感到困惑......什么样的程序员会送出一份空的生日礼物?有点意思
  • @Jacksonkr 还是不行。
【解决方案3】:

Swift 非常重视类型安全。整个 Swift 语言的设计都考虑到了安全性。这是 Swift 的标志之一,你应该张开双臂欢迎它。它将有助于开发干净、可读的代码,并帮助防止您的应用程序崩溃。

Swift 中的所有选项都用? 符号划分。通过在您声明为可选的类型名称之后设置?,您实际上强制转换这不是?之前的类型,而是作为可选类型。


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


使用可选

var myString: String?

myString = "foobar"

意味着您正在使用String 类型。这意味着您正在使用String? 类型(可选字符串或可选字符串)。事实上,每当你尝试

print(myString)

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

展开一个可选的意味着你现在将该类型转换为非可选的。这将生成一个新类型并将驻留在该可选类型中的值分配给新的非可选类型。通过这种方式,您可以对该变量执行操作,因为编译器已保证该变量具有可靠的值。


Conditionally Unwrapping 将检查可选项中的值是否为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 代码。它打印出设置为“foobar”的myString 的值。用户会看到foobar 打印在控制台中,仅此而已。但是让我们假设该值从未设置:

var myString: String?

print(myString!) 

现在我们面临不同的情况。与 Objective-C 不同,每当尝试强制解包一个可选项,并且该可选项尚未设置且为 nil,当您尝试解包可选项以查看应用程序内部的内容时,将会崩溃。


使用类型转换解包。正如我们之前所说,虽然您是 unwrapping 可选的,但实际上您正在强制转换为非可选类型,您也可以将非可选类型强制转换为不同的类型。例如:

var something: Any?

在我们的代码中,变量something 将被设置为某个值。也许我们正在使用泛型,或者可能有一些其他的逻辑正在发生,这会导致这种情况发生变化。所以稍后在我们的代码中,我们想使用something,但如果它是不同的类型,仍然能够以不同的方式对待它。在这种情况下,您将需要使用 as 关键字来确定:

注意: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

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

为了在某种排序或数学运算中使用这些变量,必须将它们解包。

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

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

现在是Optional(0)

如果你曾经尝试过写一些类似

的作业
1 + Optional(1) = 2

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

安全第一的孩子。

【讨论】:

  • 对可选类型的很好解释。帮我解决了问题。
  • 我不明白。
【解决方案4】:

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

'!' 表示强制值

【讨论】:

    【解决方案5】:

    在 Swift 中有 7 种方法可以解开可选值。

    1. 可选绑定 - 安全
    2. 可选链接 - 安全
    3. 可选模式 - 安全
    4. 无合并 - 安全
    5. 守卫声明 - 安全
    6. 强制展开 - 不安全
    7. 隐式展开 - 不安全

    【讨论】:

      猜你喜欢
      • 2023-03-08
      • 2014-07-23
      • 2011-12-03
      • 2010-10-27
      • 2011-01-28
      • 1970-01-01
      • 2020-10-05
      • 1970-01-01
      相关资源
      最近更新 更多