闭包不能隐式捕获一个变异的自参数

我正在使用Firebase观察事件,然后在完成处理程序中设置图像

FirebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in if let _ = snapshot.value as? NSNull { self.img = UIImage(named:"Some-image")! } else { self.img = UIImage(named: "some-other-image")! } }) 

但是我得到这个错误

闭包不能隐式捕获一个变异的自参数

我不确定这个错误是什么,寻找解决scheme没有帮助

简短的版本

拥有对FirebaseRef.observeSingleEvent(of:with:)调用的types很可能是值types( struct ?),在这种情况下,变异上下文可能不会在@escaping闭包中明确地捕获self

简单的解决scheme是将你的拥有types更新为一次引用( class )。


更长的版本

Firebase的observeSingleEvent(of:with:)方法声明如下

 func observeSingleEvent(of eventType: FIRDataEventType, with block: @escaping (FIRDataSnapshot) -> Void) 

blockclosures被标记为@escaping参数属性,这意味着它可能会逃脱其function的身体,甚至self的生命(在你的上下文中)。 利用这些知识,我们构build一个更简单的例子,我们可以分析:

 struct Foo { private func bar(with block: @escaping () -> ()) { block() } mutating func bax() { bar { print(self) } // this closure may outlive 'self' /* error: closure cannot implicitly capture a mutating self parameter */ } } 

现在,错误信息变得更加明显,我们转向Swift 3中实现的以下演化提议:

  • SE-0035:将捕获限制在@noescape上下文中

陈述[强调我的]:

捕获一个inout参数, 包括self在一个变异的方法 ,成为一个可变的闭合文字错误, 除非捕获是明确的 (从而不可变)。

现在,这是一个关键点。 对于一个types(例如struct ),我认为在你的例子中对拥有对observeSingleEvent(...)的调用的types也是这种情况,这样的显式捕获是不可能的,afaik(因为我们正在值types,而不是引用)。

对这个问题最简单的解决scheme将使types拥有observeSingleEvent(...)一个引用types,例如一个class ,而不是一个struct

 class Foo { init() {} private func bar(with block: @escaping () -> ()) { block() } func bax() { bar { print(self) } } } 

只要注意,这将通过强有力的参考来捕捉self ; 取决于你的上下文(我没有使用Firebase我自己,所以我不知道),你可能想明确地捕捉self弱,例如

 FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ... 

如果有人正在search这个页面,并且你正在定义一个protocol / protocol extension ,那么如果你把你的protocol声明为类绑定 ,这可能会有所帮助。 喜欢这个:

 protocol MyProtocol: class { ... } 

同步解决scheme

如果你需要在闭包中改变一个值types( struct ),那么它可能只能同步工作,而不能用于asynchronous调用,如果你这样写的话:

 struct Banana { var isPeeled = false mutating func peel() { var result = self SomeService.synchronousClosure { foo in result.isPeeled = foo.peelingSuccess } self = result } } 

除了提供一个可变(因此是var )的副本之外,你不能以其他方式捕捉具有值types的“变异自我”。

为什么不是asynchronous?

这在asynchronous上下文中不起作用的原因是:您仍然可以在没有编译器错误的情况下对result进行变异,但是您不能将变异的结果赋值给self 。 尽pipe如此,仍然没有错误,但是self将永远不会改变,因为方法( peel() )在closures被调度之前退出。

为了避免这种情况,您可以尝试更改代码,以便通过等待asynchronous完成来将asynchronous调用更改为同步执行。 尽pipe在技术上可行,但这可能会破坏您与之交互的asynchronousAPI的目的,您最好改变方法。

改变struct class是一个技术上合理的select,但没有解决真正的问题。 在我们的例子中,现在是class Banana ,其属性可以asynchronous地更改谁知道什么时候。 这会造成麻烦,因为很难理解。 你最好在模型本身之外编写一个API处理程序,并在完成执行时取回并更改模型对象。 没有更多的背景,很难举一个合适的例子。 (我假设这是模型代码,因为self.img在OP的代码中被突变了。)

添加“asynchronous反腐败”对象可能会有所帮助

我正在考虑这样的事情:

  • BananaNetworkRequestHandlerasynchronous执行请求,然后将结果BananaPeelingResult报告给BananaStore
  • 然后BananaStore通过寻找peelingResult.bananaID从其内部获取适当的Banana
  • find一个对象与banana.bananaID == peelingResult.bananaID ,然后设置banana.isPeeled = peelingResult.isPeeled
  • 最后用变异的实例replace原始对象。

你看,从寻求一个简单的修复,它可以变得相当容易,特别是如果必要的变化包括改变应用程序的体系结构。

另一个解决scheme是明确地捕获自我(因为在我的情况下,我在一个协议扩展的变异函数,所以我不能容易地指定这是一个引用types)。

所以,而不是这个:

 functionWithClosure(completion: { _ in self.property = newValue }) 

我有这个:

 var closureSelf = self functionWithClosure(completion: { _ in closureSelf.property = newValue }) 

这似乎已经使这个警告无声了。

请注意,这不适用于值types,所以如果self是值types,则需要使用引用types包装器才能使此解决scheme起作用。