我正在使用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")!
}
})
但是我收到此错误
Closure不能隐式捕获变异的自身参数
我不确定这个错误是什么,搜索解决方案没有帮助
答案 0 :(得分:58)
简短版
拥有对FirebaseRef.observeSingleEvent(of:with:)
的调用的类型很可能是值类型(struct
?),在这种情况下,变异上下文可能无法明确捕获self
中的@escaping
闭包。
简单的解决方案是将您拥有的类型更新为引用一次(class
)。
版本较长
observeSingleEvent(of:with:)
method of Firebase声明如下
func observeSingleEvent(of eventType: FIRDataEventType, with block: @escaping (FIRDataSnapshot) -> Void)
block
闭包用@escaping
参数属性标记,这意味着它可以转义其函数体,甚至是self
的生命周期(在您的上下文中)。使用这些知识,我们构建了一个我们可以分析的更小的例子:
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中实现:
陈述[强调我的]:
在变异中捕获
inout
参数,包括self
方法,成为一个可逃避的封闭文字中的错误,除非 捕获是明确的(从而不可变)。
现在,这是一个关键点。对于值类型(例如struct
),我相信在您的示例中拥有对observeSingleEvent(...)
的调用的类型也是如此,这样的显式捕获不是可能的,afaik(因为我们使用的是值类型,而不是参考值)。
此问题的最简单解决方案是使类型拥有observeSingleEvent(...)
引用类型,例如一个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 ...
答案 1 :(得分:13)
如果你需要改变一个闭包中的值类型(struct
),它可能只能同步工作,但不能用于异步调用,如果你这样写:
struct Banana {
var isPeeled = false
mutating func peel() {
var result = self
SomeService.synchronousClosure { foo in
result.isPeeled = foo.peelingSuccess
}
self = result
}
}
除非提供一个可变的(因此var
)副本,否则你无法以值类型捕获“变异自我”。
这在异步上下文中不起作用的原因是:您仍然可以在没有编译器错误的情况下变更result
,但是您无法将变异的结果分配回self
。仍然没有错误,但self
永远不会改变,因为方法(peel()
)在调度闭包之前退出。
为避免这种情况,您可以尝试更改代码,以通过等待它完成来将异步调用更改为同步执行。虽然技术上可行,但这可能会破坏您正在与之交互的异步API的目的,并且您最好改变您的方法。
将struct
更改为class
是技术上合理的选择,但并未解决实际问题。在我们的示例中,现在是class Banana
,其属性可以异步更改who-know-when。这会带来麻烦,因为它很难理解。 最好在模型本身之外编写API处理程序,并在完成执行获取并更改模型对象之后。如果没有更多上下文,很难给出一个合适的示例。 (我假设这是模型代码,因为self.img
在OP的代码中发生了变异。)
我正在考虑其中的一些内容:
BananaNetworkRequestHandler
异步执行请求,然后将生成的BananaPeelingResult
报告回BananaStore
BananaStore
然后通过查找Banana
peelingResult.bananaID
banana.bananaID == peelingResult.bananaID
的对象后,会设置banana.isPeeled = peelingResult.isPeeled
,你会发现,从寻找简单修复的过程中,它可以很容易地参与其中,特别是如果必要的更改包括更改应用程序的体系结构。
答案 2 :(得分:11)
如果有人绊倒此页面(来自搜索)并且您要定义protocol
/ protocol extension
,那么如果您将protocol
声明为类绑定,则可能会有所帮助。像这样:
protocol MyProtocol: class
{
...
}
答案 3 :(得分:0)
您可以尝试一下!希望对您有所帮助。
struct Mutating {
var name = "Sen Wang"
mutating func changeName(com : @escaping () -> Void) {
var muating = self {
didSet {
print("didSet")
self = muating
}
}
execute {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15, execute: {
muating.name = "Wang Sen"
com()
})
}
}
func execute(with closure: @escaping () -> ()) { closure() }
}
var m = Mutating()
print(m.name) /// Sen Wang
m.changeName {
print(m.name) /// Wang Sen
}
答案 4 :(得分:-11)
另一个解决方案是明确捕获self(因为在我的情况下,我处于协议扩展的变异函数中,所以我不能轻易指定这是一个引用类型。)
所以不要这样:
functionWithClosure(completion: { _ in
self.property = newValue
})
我有这个:
var closureSelf = self
functionWithClosure(completion: { _ in
closureSelf.property = newValue
})
这似乎使警告无声。
注意这对值类型不起作用,因此如果self是值类型,则需要使用引用类型包装器才能使此解决方案起作用。