如果让在显式指定类型时表现得很奇怪

if let acting weird when explicitly specifying type

提问人:Ilea Cristian 提问时间:12/20/2014 最后编辑:Ilea Cristian 更新时间:12/20/2014 访问量:344

问:

假设我们有:

let a:Int? = nil

// block not executed - unwapping done, type is inferred
if let unwrapped_a = a {
    println(unwrapped_a)
}

// block not executed - unwrapping done, type is specified
if let unwrapped_a:Int = a {
    println(unwrapped_a)
}

// block gets executed - unwrapping not done, new local constant + assignment is done instead? 
if let not_unwrapped_a:Int? = a {
    println(not_unwrapped_a)
}

那么,我是否应该假设 Swift 在第一种情况下进行解包,但在第二种情况下进行赋值?

这种语法是不是有点太接近了,不会造成混淆?我的意思是,是的,编译器警告您在使用 时使用的是可选类型,但仍然如此。not_unwrapped_a

更新:

因此,在 Airspeed Velocity 的回答之后,我发现了另一个(但实际上相同)奇怪的案例:

if let not_unwrapped_a:Int???? = a {
    println(not_unwrapped_a)
}

a将默默地包裹在.所以它将是一种 (五) - 因为已经是可选的。然后它会被解开一次。Int????Int?????a

iOS Swift 语法 选项类型

评论

2赞 mc01 12/20/2014
与下面的安东尼奥相呼应,最后一个案例在我看来是多余的和不必要的。其目的是用于解包可选项的简写,并且仅在非 none 时执行。第三种情况是说“如果这个 nil/non-nil 值是 nil 或 non-nil(当然是),无论如何都要执行块”,这就是发生的事情。这实际上与使用常规语句相同,绕过在 nil 值上正常失败时提供的安全功能。无论如何,您都必须在块内再次处理值,这违背了可选绑定和可选绑定的目的。if letifif letnil

答:

4赞 Antonio 12/20/2014 #1

可选绑定的目的是检查一个可选的不为零,解开并分配给非可选的类型,该类型包含在可选中。所以在我看来,第一种情况是正确的使用方式 - 我唯一会使用的变体是添加一个可选的强制转换(例如,当可选包含时很有用)。AnyObject

我不会使用第二种情况,更喜欢可选的演员:

if let unwrapped_a = a as? Int { ... }

除非,正如 @drewag 在注释中指出的那样,明确指定类型以避免在不清楚时产生歧义。

在我看来,第三种情况应该会产生编译错误。我没有看到任何使用可选绑定将一个可选分配给另一个可选。可选绑定不是通用的内联赋值(例如 ),因此从概念上讲,如果左侧是可选的,则它不应该起作用 - 它的处理方式应该与右侧不是可选的(编译失败)相同。if let x = 5 {...}

评论

3赞 drewag 12/20/2014
如果您认为类型有助于提高可读性很重要,那么使用第二种情况是完全有效和合理的。有时,在查看本地化上下文时,不清楚变量是什么类型,一些额外的注释,即使没有必要,也可以提高可读性。将其作为可选强制转换将意味着 a 还不是 类型。这可能会使代码更加混乱。Int
0赞 Antonio 12/20/2014
@drewag:我同意这将使代码不那么模棱两可,是的,强制转换通常被解释为“它可以是另一种类型”。不过,这只是个人喜好,我不会使用它,因为我认为周围的代码应该可以解决歧义。但是我正在更新答案,你说的很重要
0赞 Airspeed Velocity 12/20/2014
在这种情况下,这很令人困惑,甚至可能没有帮助,但我认为这不应该是编译器错误,Swift 只是始终如一地将其隐式升级逻辑应用于可选值。特殊的大小写错误可能会因不一致而导致更多的混乱。
5赞 Airspeed Velocity 12/20/2014 #2

案例 1 和案例 2 是相同的——它们都是对新变量的内容赋值。唯一的区别是,你让 Swift 推断选项 1 中的类型,而你手动给出选项 2 中的类型。您需要执行选项 2 的主要原因是源值是否不明确 - 例如,如果它是一个可以返回多个类型的重载函数。aunwrapped_a

案例 3 非常有趣。

每当你有一个值时,Swift 总是愿意默默地将它升级为一个可选的包装值,如果它有助于使类型匹配和代码编译。类型的快速自动升级是相当罕见的(例如,它不会隐式地将 an 升级为 an),但将值升级为 optionals 是一个例外。Int16Int32

这意味着您可以在任何需要可选值的地方传递值,而不必费心包装它:

func f(maybe: Int?) { ... }

let i = 1

// you can just pass a straight value:
f(i)

// Swift will turn this into this:
f(Optional(i))

所以在你的最后一个例子中,你已经告诉 Swift 你想成为一个 .但它是 a 的一部分,在分配给它之前需要解开它。not_unwrapped_aInt?leta

有了这个,Swift 让它工作的唯一方法就是隐式包装另一个可选,所以这就是它的作用。现在它是一个 optional 包含一个 optional containing nil。这不是一个零值的可选 - 即一个包含值的可选(一个包含零的可选)。展开后,可为您提供一个可选的包含 nil。好像什么都没发生。但它确实做到了——它被包裹了第二次,然后被解开了一次。a

如果使用 编译示例代码,则可以看到这一点。你会看到短语 .是可选的,包含可选的。swiftc -dump-ast source.swiftinject_into_optional implicit type='Int??’Int??

包含可选的可选不是晦涩难懂的边缘情况 - 它们很容易发生。例如,如果你曾经...在包含可选项的数组中,或者使用下标从包含可选项的字典中获取值,则该过程中涉及可选项的可选项。

另一种思考方式是,如果你把 看作是一个函数,定义如下:if let x = y { }if_let

func if_let<T>(optVal: T?, block: T->()) {
    if optVal != nil {
        block(optVal!)
    }
}

现在想象一下,如果你提供一个需要 - 也就是说,将是一个 .所以会是一个.当你将一个常规代码与该块一起传递时,Swift 会隐式地将其升级为 an 以使其编译。这基本上就是 .blockInt?TInt?T?Int??Int?if_letInt??if let not_unwrapped_a:Int?

我同意,隐含的可选升级有时会令人惊讶(更令人惊讶的是,Swift 将升级返回可选的函数,即如果一个函数需要 ,它将升级 an 以返回可选)。但据推测,在这种情况下,为了方便起见,潜在的混淆是值得的。(Int)->Int?(Int)->Int

* 只有一种