Swift 3不正确的字符串插值,带有隐式展开的Optionals


71

为什么隐式解开了可选在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")

当然,我可以将字符串与 +运算符将字符串但是我在应用程序中的几乎所有地方都使用了字符串插值,由于这个原因,现在不再起作用了(错误?)。

这甚至是一个错误,还是他们有意使用Swift 3更改了此行为?


4
请注意,这很快就会产生警告:github.com/apple/swift/pull/5110
jtbandes

1
这是我认为最干净的解决方案,它是更好,因为它明确地说,当发生了什么strnilprint("The following should not be printed as an optional: \(str ?? "")")
哈希米

2
@hashemi这里的核心问题是根本不需要这样做。IUO的全部要点是,您知道它在被访问时将始终具有一个值,因此您无需执行显式nil检查。而且-顾名思义-编译器应该为您完成所有拆包工作,但不会。
京湾2016年

Ole Begemann关于3.0和3.1中的可选项和字符串插值(虽然不是专门针对IUO)oleb.net/blog/2016/12/optionals-string-interpolation
hashemi

您好Keiwan,您只需要把放!在打印语句中这样的字符串变量旁边,print(“以下内容不应作为可选内容打印:(str!)”)
Nand Parikh

Answers:


71

根据SE-0054ImplicitlyUnwrappedOptional<T>不再是唯一类型;只有Optional<T>现在。

声明仍然可以被注释为隐含的未包装的可选内容T!,但这只是添加一个隐藏的属性来通知编译器,在需要他们的未包装类型的上下文中,它们的值可能被强制包装T; 他们的实际类型是现在T?

因此,您可以想到以下声明:

var str: String!

实际看起来像这样:

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

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

// `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.

而且,与强制展开相比,编译器将始终更喜欢将其视为此类。

如提案所述(强调我的意思):

如果可以使用强可选类型对表达式进行显式类型检查,则它将为。但是,如果需要的话,类型检查器将转为强制使用可选。此行为的结果是,引用声明为的值的任何表达式的结果T!将具有typeT或typeT?

对于字符串插值,在后台,编译器使用_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不会强行解开的值,您最终将看到可选内容的描述。

如果希望在字符串插值中使用时将IUO强制展开,则可以简单地使用force unwrap运算符!

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)")

如果str为,这两个当然都会崩溃nil


29
好像您必须使用IUO还不够糟糕,现在它们的用处不大,而且行为矛盾。如果我使用它,那是因为我希望将其隐式展开。从现在开始,它们应被称为SchrödingerOptionals。
安德里亚斯

5
只是不要使用它们。👍🏻–
亚历山大

6
给出的解释很好。不需要给出这种解释的事实。我使用Swift的次数越多,给我的印象就越多,它们只是将objc中的一种复杂性简单地替换为另一种类型,而最终,使用其中的一种也同样困难。这种东西如何使程序员的生活更轻松,这超出了我的范围。
乔里斯·芒斯

在这种东西和他们对XCode的Swift支持的热爱之间,很难相信这是Apple推动我们使用的语言
la_f0ka

6
与Obj-C相比,我出乎意料地爱Swift,但我必须承认,看似我的编码工作中有75%直接或间接地涉及了对可选变量要求的纠正。对于一个足够简单的问题,这是一件令人费解的工作:对象是否为零?当然,肯定有更好的选择(双关语)。
提请。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.