【问题标题】:Why is using isEmpty preferable over a comparison with an empty string literal is Swift?为什么使用 isEmpty 比与空字符串文字的比较更可取的是 Swift?
【发布时间】:2020-05-19 20:52:26
【问题描述】:

String 类型是 Swift 有一个名为isEmpty 的属性,表示字符串是否没有字符。

我想知道使用isEmpty 和检查空字符串文字是否相等之间是否有任何区别。换句话说,myString.isEmpty 是否比 myString == "" 更好?

我做了一些研究,发现了以下建议:

  1. Apple 开发者文档中的String struct reference(以及Swift Language Guide)建议使用isEmpty 而不是检查字符串的长度:

要检查字符串是否为空,请使用其isEmpty 属性,而不是将其中一个视图的长度与0 进行比较。与 isEmpty 不同,计算视图的 count 属性需要遍历字符串的元素。

  1. Answer to a slightly different question Rob Napier 于 2015 年在 StackOverflow 上发表声明如下:

    空字符串是唯一的空字符串,因此不应出现string.isEmpty() 不返回与string == "" 相同的值的情况。当然,他们可能会在不同的时间和记忆中这样做。它们是否使用不同的时间和内存量是未描述的实现细节,但isEmpty 是检查 Swift 的首选方式(如文档所述)。

  2. 一些博文还建议使用isEmpty,尤其是不要检查字符串的长度是否为0。

然而,这些来源都没有说反对与空文字进行比较。

由于明显的性能和可读性缺陷,避免像 myString.count == 0 这样的结构似乎是完全合理的。我还知道myString.isEmptymyString == "" 更具可读性。

不过,我很好奇与空字符串文字的比较是否不好。它真的对内存或性能有任何影响吗?也许现在 Swift 编译器非常聪明,它会为myString.isEmptymyString == "" 生成相同的二进制代码?我的直觉是差异应该可以忽略不计甚至不存在,但我没有证据。

我意识到这并不是一个真正的实际问题,但是如果有人能分享一些见解,这两种替代方案如何在较低级别上工作以及是否存在任何差异,我将不胜感激。提前谢谢大家。

【问题讨论】:

  • 我真的不知道,但字符串是字符的集合,因为据我所知,swift 中的所有其他集合类型也有一个 isEmpty 属性,所以 String 也有一个是有道理的。而且我个人喜欢在检查它们的实例是否为空时能够为不同类型使用相同属性的一致性

标签: swift swift-string


【解决方案1】:

请注意,isEmpty 是检查集合是否为空的首选/推荐方法,因为所有 Collection 类型都保证 isEmpty 在 O(1) 中返回(或者至少这适用于标准图书馆收藏)。相等运算符不做这样的保证,因此,如果您只对集合有或没有元素感兴趣(例如,启动处理操作),那么isEmpty 绝对是要走的路。

现在,要查看使用 isEmpty 与比较空字符串时的幕后情况,我们可以使用生成的程序集。

func testEmpty(_ str: String) -> Bool { str.isEmpty }

产生以下汇编代码:

                     _$s3CL29testEmptyySbSSF:
0000000100002c70         push       rbp
0000000100002c71         mov        rbp, rsp
0000000100002c74         mov        rax, rsi
0000000100002c77         shr        rax, 0x38
0000000100002c7b         and        eax, 0xf
0000000100002c7e         movabs     rcx, 0xffffffffffff
0000000100002c88         and        rcx, rdi
0000000100002c8b         bt         rsi, 0x3d
0000000100002c90         cmovae     rax, rcx
0000000100002c94         test       rax, rax
0000000100002c97         sete       al
0000000100002c9a         pop        rbp
0000000100002c9b         ret        

同时

func testEqual(_ str: String) -> Bool { str == "" }

生成这个:

                     _$s3CL29testEqualySbSSF:
0000000100002cd0         push       rbp
0000000100002cd1         mov        rbp, rsp
0000000100002cd4         movabs     rcx, 0xe000000000000000
0000000100002cde         test       rdi, rdi
0000000100002ce1         jne        0x100002cec

0000000100002ce3         cmp        rsi, rcx
0000000100002ce6         jne        0x100002cec

0000000100002ce8         mov        al, 0x1
0000000100002cea         pop        rbp
0000000100002ceb         ret        

0000000100002cec         xor        edx, edx                                    ; XREF=_$s3CL29testEqualySbSSF+17, _$s3CL29testEqualySbSSF+22
0000000100002cee         xor        r8d, r8d
0000000100002cf1         pop        rbp
0000000100002cf2         jmp        imp___stubs__$ss27_stringCompareWithSmolCheck__9expectingSbs11_StringGutsV_ADs01_G16ComparisonResultOtF
                        ; endp

两个程序集均在发布模式下生成,并启用了所有优化。似乎对于isEmpty 调用,编译器能够采取一些捷径,因为它知道内部String 结构。

但我们可以通过使函数通用化来消除这一点:

func testEmpty<S: StringProtocol>(_ str: S) -> Bool { str.isEmpty }

生产

                     _$s3CL29testEmptyySbxSyRzlF:
0000000100002bd0         push       rbp
0000000100002bd1         mov        rbp, rsp
0000000100002bd4         push       r13
0000000100002bd6         push       rax
0000000100002bd7         mov        rax, rsi
0000000100002bda         mov        rcx, qword [ds:rdx+8]
0000000100002bde         mov        rsi, qword [ds:rcx+8]
0000000100002be2         mov        r13, rdi
0000000100002be5         mov        rdi, rax
0000000100002be8         call       imp___stubs__$sSl7isEmptySbvgTj
0000000100002bed         add        rsp, 0x8
0000000100002bf1         pop        r13
0000000100002bf3         pop        rbp
0000000100002bf4         ret        
                        ; endp

,而

func testEqual<S: StringProtocol>(_ str: S) -> Bool { str == "" }

生产

                     _$s3CL29testEqualySbxSyRzlF:
0000000100002c00         push       rbp
0000000100002c01         mov        rbp, rsp
0000000100002c04         push       r14
0000000100002c06         push       r13
0000000100002c08         push       rbx
0000000100002c09         sub        rsp, 0x18
0000000100002c0d         mov        r14, rdx
0000000100002c10         mov        r13, rsi
0000000100002c13         mov        rbx, rdi
0000000100002c16         mov        qword [ss:rbp+var_28], 0x0
0000000100002c1e         movabs     rax, 0xe000000000000000
0000000100002c28         mov        qword [ss:rbp+var_20], rax
0000000100002c2c         call       _$sS2SSysWl
0000000100002c31         mov        rcx, qword [ds:imp___got__$sSSN]
0000000100002c38         lea        rsi, qword [ss:rbp+var_28]
0000000100002c3c         mov        rdi, rbx
0000000100002c3f         mov        rdx, r13
0000000100002c42         mov        r8, r14
0000000100002c45         mov        r9, rax
0000000100002c48         call       imp___stubs__$sSysE2eeoiySbx_qd__tSyRd__lFZ
0000000100002c4d         add        rsp, 0x18
0000000100002c51         pop        rbx
0000000100002c52         pop        r13
0000000100002c54         pop        r14
0000000100002c56         pop        rbp
0000000100002c57         ret        
                        ; endp

类似的结果,isEmpty 代码结果是更少的汇编代码,这使得它更快。

【讨论】:

  • 哇,非常感谢您提供非常详细的答案,非常感谢您花时间研究这个。我知道在现实生活中性能差异可以忽略不计,但很高兴知道编译器会为 == ""isEmpty 生成不同的二进制代码,而后者的性能要高一点。
  • @VadimBelyaev isEmpty 也是检查集合是否为空的首选方法,因为所有 Collection 类型都保证 isEmpty 在 O(1) 中返回(或者至少这适用于标准库集合)。相等运算符不做这样的保证,因此,如果您只对集合有或没有元素感兴趣(例如,启动处理操作),那么isEmpty 绝对是要走的路。
  • Cristik,关于收藏的好点。但是,通过将集合与空集合进行比较来检查集合的空性并不太习惯(更常见的错误是检查是否为.count == 0),但是根据我的经验,将字符串与空字符串文字进行比较更为常见。尽管如此,由于字符串也是集合,.isEmpty 在所有情况下确实似乎更可取。
猜你喜欢
  • 1970-01-01
  • 2022-03-30
  • 2011-06-21
  • 2012-02-09
  • 2016-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多