Swift 可选链在闭包中不起作用

Posted

技术标签:

【中文标题】Swift 可选链在闭包中不起作用【英文标题】:Swift optional chaining doesn't work in closure 【发布时间】:2014-06-09 17:13:38 【问题描述】:

我的代码如下所示。我的班级有一个可选的 var

var currentBottle : BottleLayer?

BottleLayer 有一个方法jiggle()

这段代码使用可选链接,在我的类中编译得很好:

self.currentBottle?.jiggle()

现在我想构造一个使用相同代码的闭包:

let clos = () -> () in self.currentBottle?.jiggle()

但是我得到一个编译错误:

找不到成员'jiggle'

作为一种解决方法,我可以强制展开

let clos = () -> () in self.currentBottle!.jiggle()

或者我当然可以使用成熟的可选绑定,但我宁愿不这样做。我确实认识到可选链接只是语法糖,但很难理解为什么这种语法糖会因为它在处理程序中而停止工作(当然,这可能是一个原因 - 但无论如何这都是一个惊喜) .

也许其他人对此有所了解并对此有想法?谢谢。

【问题讨论】:

是的,我已经提交了一份错误报告,只是因为这是语言中的错误。但这似乎不太可能。 :) 我认为它一个错误,如果强制展开有效,那么可选链接也应该 这根本不是错误。这仅仅是因为您明确键入了闭包……但是类型错误(应该是()->()? 而不是()->())。如果您要删除显式类型并让 Swift 为您推断它,您可以确认该类型是 Void->Void?。有关详细说明,请参阅下面的答案。 【参考方案1】:

这不是错误。这只是你的闭包类型是错误的。 正确的类型应该返回一个可选的Void 以反映可选的链接:

let clos =  ()->()? in currentBottle?.jiggle() 

问题详情:

您将闭包声明为返回Void(即->())的闭包。 但是,请记住,就像每次使用可选链接一样,整个表达式的返回类型是可选类型。因为如果currentBottle 确实存在,您的闭包可以返回Void……如果不存在,则返回nil

所以正确的语法是让你的闭包返回一个Void?(或()?)而不是一个简单的Void

class BottleLayer 
    func jiggle()  println("Jiggle Jiggle") 

var currentBottle: BottleLayer?
currentBottle?.jiggle() // OK
let clos =  Void->Void? in currentBottle?.jiggle()  // Also OK
let clos =  () -> ()? in currentBottle?.jiggle()  // Still OK (Void and () are synonyms)

注意:如果您让 Swift 为您推断出正确的类型而不是显式强制它,它会为您解决问题:

// Even better: type automatically inferred as ()->()? — also known as Void->Void?
let clos =  currentBottle?.jiggle() 

[编辑]

附加技巧:直接将可选链接分配给变量

您甚至可以将函数直接分配给变量,如下所示:

let clos2 = currentBottle?.jiggle // no parenthesis, we don't want to call the function, just refer to it

请注意,clos2 的类型(此处未明确指定,因此由 Swift 自动推断)在这种情况下 不是 Void->Void? — 即返回 @987654336 的函数@ 或 Void) 与前一种情况一样——但 (Void->Void)?,它是“Void->Void 类型的 可选 函数”的类型。 p>

这意味着clos2 本身是“nil 或者是返回 Void 的函数”。要使用它,您可以再次使用可选链接,就像这样:

clos2?()

这将评估为nil,如果clos2 本身是nil(可能是因为currentBottle 本身为零),则不执行任何操作……并执行闭包——因此currentBottle!.jiggle() 代码——并返回Void如果clos2 不为零(可能是因为currentBottle 本身不为零)。

clos2?() 本身的返回类型确实是 Void?,因为它返回 nil 或 Void。

区分VoidVoid? 似乎毫无意义(毕竟,jiggle 函数在这两种情况下都不会返回任何内容),但它可以让你做一些强大的事情,比如在一个if 语句检查调用是否确实发生(并返回 Void 即什么也没有)或没有发生(并返回 nil):

if clos2?()  println("The jiggle function got called after all!") 

[EDIT2] 正如您 (@matt) 自己指出的那样,这种另一种选择还有另一个主要区别:它在表达式受到clos2 影响时评估currentBottle?.jiggle。因此,如果当时currentBottlenil,那么clos2 将是nil……即使后来currentBottle 得到了一个非零值。

相反,clos 受到闭包本身的影响,并且可选链接仅在每次调用 clos 时才被评估,因此如果 currentBottle 为 nil,它将评估为 nil...但将评估为非零,如果我们稍后调用 clos() 将调用 jiggle(),此时 currentBottle 变为非零。

【讨论】:

这实际上让我的代码比以前更棒了。 你可以做更多很棒的事情,比如将变量直接分配给函数(函数和闭包在 Swift 中具有相同的类型)。即let clos2 = currentBottle?.jiggle。但请注意,如果您这样做,您将获得与clos 稍有不同的东西:您获得的不是Void->Void?,而是(Void->Void)?,即“可选功能”。不是“返回nilVoid 的函数”,而是“nil 或返回Void 的函数”。然后,您可以使用 clos2?() 调用它(如果 currentBottle 因此 clos2 为 nil,则不会执行任何操作) 我编辑了我的答案以更详细地解释这个额外的技巧。 第二个技巧很酷,但出于其他原因,我实际上想要闭包,而不仅仅是函数引用。现在评估currentBottle?(通过它并拉出方法ref)和稍后(通过它在我们调用闭包时,它可能是不同的对象)。 感觉SO上的一些答案比苹果的10页参考书更好更深...【参考方案2】:
let closure =  () -> () in
    self.currentBottle?.jiggle()
    return

否则编译器认为该语句的结果应该从闭包返回,它意识到()(返回类型)和语句返回的可选(Optional<Void>)之间存在不匹配。通过显式添加return,编译器将知道我们不想返回任何内容。

错误信息当然是错误的。

【讨论】:

【参考方案3】:

好的,另一种方法。这是因为 Swift 中的闭包具有单表达式闭包的隐式返回。由于可选链,您的闭包返回类型为Void?,因此:

let clos = () -> Void? in self.currentBottle?.jiggle()

【讨论】:

【参考方案4】:

推断闭包类型似乎也可以。

let clos2 =  currentBottle?.jiggle() 
clos2() // does a jiggle

但我很确定这只是编译器分配() -> ()?的类型

【讨论】:

以上是关于Swift 可选链在闭包中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

为啥 private(set) 在 Swift 中不起作用?

@babel/plugin-proposal-optional-chaining 在 Vue.js <script> 标签中不起作用

回调闭包函数在单元格中不起作用

UIActivityItemSource协议可选方法在iOS 8中不起作用

为啥类型转换在 Swift 2.0 中不起作用?

NSManagedObjectContext 在 Swift 中不起作用