闭包不能隐式捕获一个变异的自参数
我正在使用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)
block
closures被标记为@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反腐败”对象可能会有所帮助
我正在考虑这样的事情:
-
BananaNetworkRequestHandler
asynchronous执行请求,然后将结果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起作用。
- Error“Thread 1:EXC_BAD_INSTRUCTION(code = EXC_I386_INVOP,subcode = 0x0)”是什么意思?
- 在UIPageViewController中禁用反弹效果
- 即使“plist”中的“UIBackgroundModes”中存在“voip”,iOS设备在iOS10中重启设备后也不会自动启动
- Swift – 为什么init(编码器)在AFHTTPSessionManager中是必需的?
- 通过将属性设置为nil,在Swift中再次触发lazy初始化器
- 将UICollectionView单元格数据传递给不同的ViewController时EXC_BAD_INSTRUCTION
- 运行自定义shell脚本' embed pods frameworks'no such file or directory
- 我应该如何在Swift中replace这些屏幕大小和设备typesmacros?
- 在Swift中逐像素地应用视觉效果
- UITableViewRowAction标题作为图像图标而不是文本
- 在迅速find高度