"if nil != optional..."和"if let _ = optional..."有什么区别?" (译者注:这句话是题目,以下为正文)

21

我需要测试一个返回可选值的表达式是否为nil。这似乎是一件易如反掌的事情,但以下是代码。

if nil != self?.checklists.itemPassingTest({ $0 === note.object }) {
    …
}

由于某种原因,这个看起来对我不太舒服。

if let item = self?.checklists.itemPassingTest({ $0 === note.object }) {
    …
}

我认为看起来好多了,但实际上我并不需要这个物品,我只想知道是否有一个被退回。因此,我使用了以下方法。

if let _ = self?.checklists.itemPassingTest({ $0 === note.object }) {
    …
}

我有点错过了什么微妙的东西吗?我认为在这里,if nil != optional ...if let _ = optional ...是等效的。


更新以回答一些答案中的疑虑

  1. 我看不出nil != varvar != nil之间的区别,尽管我通常使用var != nil。在这种情况下,将!= nil推到块后面会使块的布尔比较与if的布尔比较混合。

  2. 使用通配符模式并不那么令人惊讶或不寻常。它们用于元组(x, _) = (10, 20)、for-in循环for _ in 1...5、case语句case (_, 0):等等(注意:这些示例取自《Swift编程语言》)。

这个问题关乎两种形式的功能等效性,而不是关于编码样式选择的问题。这个讨论可以在programmers.stackexchange.com上进行。


经过这么长时间,Swift 2.0使其无关紧要了。

if self?.checklists.contains({ $0 === note.object }) ?? false {
    …
}
4个回答

24

经过优化,这两种方法可能是相同的。

例如,在这种情况下,使用 swiftc -O -emit-assembly if_let.swift 编译以下两个代码块:

import Darwin

// using arc4random ensures -O doesn’t just
// ignore your if statement completely
let i: Int? = arc4random()%2 == 0 ? 2 : nil

if i != nil {
  println("set!")
}

vs

import Darwin

let i: Int? = arc4random()%2 == 0 ? 2 : nil

if let _ = i {
  println("set!")
}

生成相同的汇编代码:

    ; call to arc4random
    callq   _arc4random
    ; check if LSB == 1 
    testb   $1, %al
    ; if it is, skip the println
    je  LBB0_1
    movq    $0, __Tv6if_let1iGSqSi_(%rip)
    movb    $1, __Tv6if_let1iGSqSi_+8(%rip)
    jmp LBB0_3
LBB0_1:
    movq    $2, __Tv6if_let1iGSqSi_(%rip)
    movb    $0, __Tv6if_let1iGSqSi_+8(%rip)
    leaq    L___unnamed_1(%rip), %rax  ; address of "set!" literal
    movq    %rax, -40(%rbp)
    movq    $4, -32(%rbp)
    movq    $0, -24(%rbp)
    movq    __TMdSS@GOTPCREL(%rip), %rsi
    addq    $8, %rsi
    leaq    -40(%rbp), %rdi
    ; call println
    callq   __TFSs7printlnU__FQ_T_
LBB0_3:
    xorl    %eax, %eax
    addq    $32, %rsp
    popq    %rbx
    popq    %r14
    popq    %rbp
    retq

函数检查(x:Int){ /* arc4random() -> x */ }将输出从1,000页减少到1.5页。 :-) - GoZoner
在这种情况下,我并不关心性能,但知道它们具有等效的二进制表示方式让我感到放心。 - Jeffery Thomas

19
if let 语法被称为可选绑定。它将可选项作为输入,并在可选项不为 nil 时向您返回所需的常量。此功能旨在处理常见的代码模式,首先检查值是否为 nil,如果值不为 nil(有值),则对其进行操作。
如果可选项 nil,则处理停止,并跳过大括号内的代码。 if optional != nil 语法更简单。它仅检查可选项是否为 nil。它跳过为您创建必需常量的步骤。
如果您不打算使用结果值,则可选绑定语法会浪费且令人困惑。在这种情况下,请使用更简单的 if optional != nil 版本。正如 nhgrif 指出的那样,它生成的代码更少,而且您的意图更清晰。

编辑:

听起来编译器足够聪明,如果您编写“如果let”可选绑定代码但最终不使用绑定的变量,则不会生成额外的代码。主要区别在于可读性。使用可选绑定会创建一种期望使用绑定的可选项的预期。

1
考虑到在苹果的Swift示例中使用“for _ in range”的次数,我认为未绑定变量并不令人感到意外。请参考以下链接:numbertimes for _ in range - Jeffery Thomas
1
正如这位海报所说,以及我在另一条评论中补充的,源代码是人类可读的代码,因此应该表达出人类语言的逻辑。将逻辑大声朗读出来,它的意思是“如果这不是nil,那么...”,在源代码中则完全对应于“if x!= nil {...}”,这意味着更易读。老实说,我根本看不出“if let _ = x”比它有什么好处。也许这只是能够在您不关心一个或多个值时进行模式匹配的副产品,并且为了支持它,最终创建了“if let _”这种无聊的东西。 - Mark A. Donohoe
1
for _ in range是一个非常不同的情况。在这种情况下,您要运行一个循环特定次数,但不需要索引值。 - Duncan C

5
我个人认为这样做不太好,因为你是将nil与你的结果进行比较,而不是将你的结果与nil进行比较:
if self?.checklists.itemPassingTest({ $0 === note.object }) != nil {
    …
}

如果你只想确保它不是nil并且不使用item,那么使用let就没有意义。


3

AirspeedVelocity的答案向我们展示,let _ =!=nil会产生相同的汇编代码,因此我强烈建议使用第一种方法。

事实上,如果您有类似以下的内容:

if let _ = optional {
    do_something()
}

如果你想添加一些代码,并且现在需要可选项,那么这个更改将变得更加容易和快捷:

if let wrapped = optional {
    do_something()
    do_something_else(with: wrapped)
}

使用let _ =编写易维护的代码。


2
我本来想点赞的,但是后来我想到了源代码是人类可读的代码这一事实。虽然你说的未来更改值更容易,但从其他语法转换并不那么困难。更重要的是,在那之前(如果有的话),它不够易读。想想看...大声朗读逻辑时,它是在说“如果这不是nil,那么...”,这在源代码中恰好对应着“if x != nil { ...}”,这意味着后者更加易读。这就是为什么我改变了我的想法。 - Mark A. Donohoe

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