使用可选链操作符处理Swift字符串

14

使用可选链,如果我有一个Swift变量

var s: String?

字符串可能包含空值(nil),或者被封装在一个可选项(Optional)中。因此,我尝试使用以下代码来获取其长度:

let count = s?.characters?.count ?? 0

然而,编译器需要这个:

let count = s?.characters.count ?? 0

我理解的可选链是,一旦在点表达式中开始使用?.,其余的属性都变成了可选项,并且通常通过?.而不是.来访问。

所以,我进一步尝试在 playground 中运行了以下代码:

var s: String? = "Foo"
print(s?.characters)
// Output: Optional(Swift.String.CharacterView(_core: Swift._StringCore(_baseAddress: 0x00000001145e893f, _countAndFlags: 3, _owner: nil)))
结果表明s?.characters确实是一个Optional实例,这意味着s?.characters.count应该是非法的。

有人能帮我理解这种情况吗?


1
s?.characters.count(s?.characters)?.count 都可以编译通过,但是(s?.characters).count不行。第一种情况下,characters.count好像被当作一个单独的可选链,但我没有看到相关文档。 - Martin R
现在也在 [swift-users] 上讨论:https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20160801/002764.html。 - Martin R
2个回答

5
当你说:
可选链接的理解是,一旦您在点表达式中开始使用?。 ,其余属性就成为可选的,并且通常通过? 访问,而不是。
那么我会说你已经接近了。
并不是所有属性都是可选的,而是原始调用是可选的,因此看起来其他属性是可选的。
characters不是可选属性,count也不是,但您调用它的值是可选的。如果有值,则字符和数目属性将返回一个值;否则,将返回nil。正因为如此,s?.characters.count的结果返回Int?。
如果其中任何属性是可选的,则需要向其添加?,但在您的情况下,它们不是。所以你不需要。
编辑下面的注释
从评论:
我仍然觉得奇怪,s?.characters.count和(s?.characters).count都可以编译,但(s?.characters)。计数不。为什么最后一个表达式有区别?
我将在这里尝试回答它,在这里比在注释字段中有更多的空间:
s?.characters.count

如果s为nil,整个表达式返回nil,否则返回一个Int。因此返回类型是Int?

(s?.characters).count // Won’t compile

分解一下:如果 snil,那么 (s?.characters) 也是 nil,所以我们无法在其上调用 count

为了在 (s?.characters) 上调用 count 属性,表达式需要进行可选拆包,即写成:

(s?.characters)?.count

编辑以添加更多信息

我能够解释的最好的方式是通过这个游乐场代码:

let s: String? = "hello"

s?.characters.count
(s?.characters)?.count
(s)?.characters.count
((s)?.characters)?.count

// s?.characters.count
func method1(s: String?) -> Int? {
    guard let s = s else { return nil }

    return s.characters.count
}

// (s?.characters).count
func method2(s: String?) -> Int? {
    guard let c = s?.characters else { return nil }

    return c.count
}

method1(s)
method2(s)

我仍然觉得奇怪的是 s?.characters.count(s?.characters)?.count 都可以编译通过,但是 (s?.characters).count 却不能。为什么第一个和最后一个表达式之间会有差异呢? - Martin R
(s?.characters).count 中,你试图获取一个可选项的 count 属性。但在第一个中,characters 不是一个可选项。 - Viktor Simkó
@MartinR 我已经在编辑后的答案中尝试回答了这个问题。 - Abizern
1
s是可选类型。一旦成功解包,您就可以将所有其他调用链接到非可选值上。因此,如果存在,则在String上调用characters.count,否则仅返回nil。当您将其包装在括号中时,在调用外部方法之前会先计算括号内的内容。因此,您不是在未解包的值上调用characters.count,而是在可选字符串(即Optional)上调用characters,然后在必要时对结果进行解包以执行计数。 - Abizern
没有括号,characters.count 是在解包后的值上调用的。有了括号,首先评估s?.characters,然后是count。由于s?.characters作为表达式是可选的,因此在调用它的count之前必须对其进行解包。 - Abizern
显示剩余5条评论

2
在Swift用户邮件列表中,Ingo Maier很友善地向我指出了Swift语言规范中关于可选链表达式的部分,其中指出:

如果包含可选链表达式的后缀表达式嵌套在其他后缀表达式内,则只有最外层的表达式返回可选类型。

它继续给出了一个示例:

var c: SomeClass?
var result: Bool? = c?.property.performAction()

这解释了为什么编译器在上面我的例子中想要s?.characters.count,我认为这回答了原始问题。然而,正如@Martin R在评论中观察到的那样,编译器为什么会对这两个表达式进行不同的处理仍然是一个谜:
s?.characters.count
(s?.characters).count

如果我正确理解规范,子表达式为:
(s?.characters) 

意思是“嵌套在”整个后缀表达式内。
(s?.characters).count

因此,应该将其视为非括号版本并进行相同处理。但这是一个不同的问题。
感谢所有人的贡献!

正如在列表上讨论的那样,规范对问号的范围有些欠缺,但我认为swiftc做得很好。在编程语言中,使用括号来界定各种运算符/关键字的范围是相当普遍的。它通常会导致一个好的(我会说是直观的)特性,即您可以用let绑定替换带括号的表达式,并仍然具有相同的语义。使用您的示例,let x = s?.characters; x?.count(s?.characters)?.count具有相同的语义,如果(s?.characters).count是有效的Swift代码,则不是这种情况。 - ingoem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接