Swift 3不正确的字符串插值与隐式展开可选项

为什么在 Swift 3中使用字符串插值时 隐式打开的可选项没有展开?

例子 : 在操场上运行以下代码

var str: String!
str = "Hello"


print("The following should not be printed as an optional: \(str)")

产生这样的输出:

The following should not be printed as an optional: Optional("Hello")

Of course I can concatenate strings with the + operator but I'm using string interpolation pretty much everywhere in my app which now doesn't work anymore due to this (bug?).

这究竟是一个错误,还是他们故意用 Swift 3改变了这种行为?

5987 次浏览

根据 SE-0054ImplicitlyUnwrappedOptional<T>不再是一个独特的类型; 现在只有 Optional<T>

声明仍然允许被注释为隐式取消包装的选项 T!,但是这样做只是添加了一个隐藏属性来通知编译器它们的值可能在需要取消包装类型 T的上下文中被强制取消包装; 它们的实际类型现在是 T?

所以你们可以想想这个声明:

var str: String!

实际上看起来像这样:

@_implicitlyUnwrapped // this attribute name is fictitious
var str: String?

只有编译器看到这个 @_implicitlyUnwrapped属性,但是它允许在需要 String(其未包装类型)的上下文中隐式展开 str的值:

// `str` cannot be type-checked as a strong optional, so the compiler will
// implicitly force unwrap it (causing a crash in this case)
let x: String = str


// We're accessing a member on the unwrapped type of `str`, so it'll also be
// implicitly force unwrapped here
print(str.count)

但在其他所有 str可以作为强可选项进行类型检查的情况下,它将是:

// `x` is inferred to be a `String?` (because we really are assigning a `String?`)
let x = str


let y: Any = str // `str` is implicitly coerced from `String?` to `Any`


print(str) // Same as the previous example, as `print` takes an `Any` parameter.

而且编译器总是更喜欢将其视为强制展开。

正如提案所说(强调我的观点) :

如果表达式 可以使用强可选类型显式地进行类型检查,则。但是,如果需要,类型检查器将退回到强制使用可选的。这种行为的影响是 任何引用声明为 ABC0的值的表达式的结果都将具有 ABC1或 T?类型

说到字符串插值,编译器在底层使用来自 _ExpressibleByStringInterpolation协议的初始化器来计算字符串插值段:

/// Creates an instance containing the appropriate representation for the
/// given value.
///
/// Do not call this initializer directly. It is used by the compiler for
/// each string interpolation segment when you use string interpolation. For
/// example:
///
///     let s = "\(5) x \(2) = \(5 * 2)"
///     print(s)
///     // Prints "5 x 2 = 10"
///
/// This initializer is called five times when processing the string literal
/// in the example above; once each for the following: the integer `5`, the
/// string `" x "`, the integer `2`, the string `" = "`, and the result of
/// the expression `5 * 2`.
///
/// - Parameter expr: The expression to represent.
init<T>(stringInterpolationSegment expr: T)

因此,当您的代码隐式调用:

var str: String!
str = "Hello"


print("The following should not be printed as an optional: \(str)")

由于 str的实际类型是 String?,因此默认情况下编译器将推断出通用占位符 T的类型。因此,str的值不会被强制取消包装,您将最终看到一个可选的。

如果你希望在字符串插值中使用强制取消包装,你可以简单地使用强制取消包装操作符 !:

var str: String!
str = "Hello"


print("The following should not be printed as an optional: \(str!)")

或者你可以强制使用它的非可选类型(在这个例子中是 String) ,以便强制编译器隐式地强制你打开它:

print("The following should not be printed as an optional: \(str as String)")

当然,如果 strnil,两者都会崩溃。