Swift 3错误的字符串插值与隐式解包的Optionals

Kei*_*wan 71 string string-interpolation swift swift3

为什么在Swift 3中使用字符串插值时,隐式解包的选项不会被解包?

示例:在操场中运行以下代码

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str)")
Run Code Online (Sandbox Code Playgroud)

产生这个输出:

The following should not be printed as an optional: Optional("Hello")
Run Code Online (Sandbox Code Playgroud)

当然我可以用+运算符连接字符串,但我在我的应用程序中几乎无处不在使用字符串插值,现在因为这个而无法工作(bug?).

这甚至是一个bug还是他们故意用Swift 3改变这种行为?

Ham*_*ish 71

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

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

所以你可以想到这个宣言:

var str: String!
Run Code Online (Sandbox Code Playgroud)

实际上看起来像这样:

@_implicitlyUnwrapped // this attribute name is fictitious 
var str: String?
Run Code Online (Sandbox Code Playgroud)

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

// `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)
Run Code Online (Sandbox Code Playgroud)

但在所有其他情况下,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.
Run Code Online (Sandbox Code Playgroud)

并且编译器总是倾向于将其视为强制展开.

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

如果可以使用强可选类型显式地检查表达式,那么它将是.但是,如果需要,类型检查器将回退到强制可选.此行为的结果是任何表达式的结果都引用了声明为T!具有类型T或类型的值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)
Run Code Online (Sandbox Code Playgroud)

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

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str)")
Run Code Online (Sandbox Code Playgroud)

正如str实际类型一样String?,默认情况下编译器将推断通用占位符T.因此,值str不会被强制解包,您最终会看到可选的描述.

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

var str: String!
str = "Hello"

print("The following should not be printed as an optional: \(str!)")
Run Code Online (Sandbox Code Playgroud)

或者你可以强制它的非可选类型(在这种情况下String),以强制编译器隐式强制为你打开它:

print("The following should not be printed as an optional: \(str as String)")
Run Code Online (Sandbox Code Playgroud)

这两者,当然会崩溃,如果strnil.

  • 好像你不得不使用IUO那么糟糕,现在它们的用处不大,行为矛盾.如果我使用它,因为我希望它被隐式解开.从现在开始,它们应该被称为SchrödingerOptionals. (28认同)
  • 只是不要使用它们. (5认同)
  • 给出的解释很棒.需要给出这种解释的事实并非如此.我使用Swift的次数越多,我就越觉得他们只是将objc中的一种复杂性替换为另一种类型,而最终,使用其中一种方式同样困难/不好.这种东西应该如何让程序员的生活变得更轻松超出我的范围. (5认同)
  • 虽然与Obj-C相比,我出乎意料地喜欢Swift,但我必须承认,似乎75%的编码工作直接或间接地涉及纠正可选的变量要求.对于一个简单的问题来说,这是一个惊人的努力:对象是否为零?肯定会有更好的选择(双关语). (5认同)