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。
区分Void
和Void?
似乎毫无意义(毕竟,jiggle
函数在这两种情况下都不会返回任何内容),但它可以让你做一些强大的事情,比如在一个if
语句检查调用是否确实发生(并返回 Void
即什么也没有)或没有发生(并返回 nil
):
if clos2?() println("The jiggle function got called after all!")
[EDIT2] 正如您 (@matt) 自己指出的那样,这种另一种选择还有另一个主要区别:它在表达式受到clos2
影响时评估currentBottle?.jiggle
。因此,如果当时currentBottle
是nil
,那么clos2
将是nil
……即使后来currentBottle
得到了一个非零值。
相反,clos
受到闭包本身的影响,并且可选链接仅在每次调用 clos
时才被评估,因此如果 currentBottle
为 nil,它将评估为 nil
...但将评估为非零,如果我们稍后调用 clos()
将调用 jiggle()
,此时 currentBottle
变为非零。
【讨论】:
这实际上让我的代码比以前更棒了。 你可以做更多很棒的事情,比如将变量直接分配给函数(函数和闭包在 Swift 中具有相同的类型)。即let clos2 = currentBottle?.jiggle
。但请注意,如果您这样做,您将获得与clos
稍有不同的东西:您获得的不是Void->Void?
,而是(Void->Void)?
,即“可选功能”。不是“返回nil
或Void
的函数”,而是“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> 标签中不起作用