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

Posted

技术标签:

【中文标题】Swift 3 不正确的字符串插值与隐式展开的 Optionals【英文标题】:Swift 3 incorrect string interpolation with implicitly unwrapped Optionals 【发布时间】:2017-01-25 00:26:51 【问题描述】:

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

示例: 在 Playground 中运行以下代码

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 改变了这种行为?

【问题讨论】:

请注意,这很快就会产生警告:github.com/apple/swift/pull/5110 在我看来,这是最干净的解决方案,而且更好,因为它明确说明了当 strnil 时会发生什么:print("The following should not be printed as an optional: \(str ?? "")") @hashemi 这里的核心问题是这根本没有必要。 IUO 的全部意义在于你知道它在被访问时总是有一个值,因此你不需要执行显式的nil 检查。另外——顾名思义——编译器应该为你做所有的解包,但它没有。 Ole Begemann 谈 3.0 和 3.1 中的选项和字符串插值(但不是专门针对 IUO)oleb.net/blog/2016/12/optionals-string-interpolation 嗨Keiwan,你只需要放!像这样在 print 语句中的字符串变量旁边, print("以下内容不应作为可选内容打印:(str!)") 【参考方案1】:

根据SE-0054,ImplicitlyUnwrappedOptional<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.

编译器总是更喜欢这样对待它而不是强制展开。

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

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

当涉及到字符串插值时,编译器在后台使用_ExpressibleByStringInterpolation protocol 中的这个初始化程序来评估字符串插值段:

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

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

【讨论】:

好像你必须使用 IUO 还不够糟糕,但现在它们的用处不大并且表现得矛盾。如果我使用一个,那是因为我希望它被隐式展开。从现在开始,它们应该被称为 Schrödinger Optionals。 不要使用它们。 ?? 给出的解释很棒。需要给出这种解释的事实并非如此。我与 Swift 合作的次数越多,我的印象就越深,他们只是将 objc 中的一种复杂性替换为另一种类型,最后,使用其中一种或另一种同样困难/容易。这种东西应该如何让程序员的生活更轻松,这超出了我的理解。 在这类东西和他们在 XCode 中对 Swift 支持的热爱之间,很难相信这是 Apple 推动我们使用的语言 虽然与 Obj-C 相比,我意外地喜欢 Swift,但我必须承认,我的编码工作中似乎 75% 直接或间接涉及更正可选变量要求。对于一个足够简单的问题来说,这是一个惊人的努力:一个对象是 nil 还是不是?肯定有更好的选择(双关语)。

以上是关于Swift 3 不正确的字符串插值与隐式展开的 Optionals的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C self->_ivar 访问与显式与隐式 self->

显式转换与隐式转换

在隐式展开可选值 AVAUDIO Player SWIFT 时意外发现 nil

在 Apple Swift 中,在啥情况下我不想要一个隐式展开的可选项?

js高级_显示原型与隐式原型

显示类型与隐式类型