我正在使用 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")!
}
})
但是我收到此错误
闭包无法隐式捕获变异的 self 参数
我不确定这个错误是什么,并且搜索解决方案没有帮助
精简版
调用
FirebaseRef.observeSingleEvent(of:with:)
的类型很可能是值类型(struct
?),在这种情况下,变异上下文可能不会在 self
闭包中显式捕获 @escaping
。
简单的解决方案是将您的拥有类型更新为引用一次 (
class
)。
加长版
Firebase的
observeSingleEvent(of:with:)
方法声明如下
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
现在,这是一个关键点。对于 value 类型(例如
struct
),我相信在您的示例中拥有对 observeSingleEvent(...)
的调用的类型也是如此,据我所知,这样的显式捕获是不可能的(因为我们正在使用一种值类型,而不是引用类型)。
此问题最简单的解决方案是将拥有
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 ...
如果您需要在闭包中改变值类型(
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
,它的属性可以在谁知道什么时候异步更改。这会带来麻烦,因为它很难理解。 您最好在模型本身之外编写 API 处理程序,并在执行完成后获取并更改模型对象。如果没有更多上下文,就很难给出合适的示例。 (我假设这是模型代码,因为 self.img
在 OP 代码中发生了变异。)
我正在思考其中的一些内容:
BananaNetworkRequestHandler
异步执行请求,然后将结果 BananaPeelingResult
报告回 BananaStore
BananaStore
通过寻找 Banana
从其内部取出适当的
peelingResult.bananaID
banana.bananaID == peelingResult.bananaID
的对象后,它会设置 banana.isPeeled = peelingResult.isPeeled
,您会发现,从寻找简单修复的过程中,它可能会变得非常复杂,特别是如果必要的更改包括更改应用程序的架构的话。
如果有人(通过搜索)偶然发现此页面,并且您正在定义
protocol
/ protocol extension
,那么如果您将 protocol
声明为 类绑定 可能会有所帮助。像这样:
protocol MyProtocol: AnyObject {
...
}
你可以试试这个!希望能帮到你。
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
}
另一个解决方案是显式捕获 self (因为在我的例子中,我处于协议扩展的变异函数中,所以我无法轻松指定这是一个引用类型)。
所以代替这个:
functionWithClosure(completion: { _ in
self.property = newValue
})
我有这个:
var closureSelf = self
functionWithClosure(completion: { _ in
closureSelf.property = newValue
})
这似乎消除了警告。
请注意,这不适用于值类型,因此如果 self 是值类型,则需要使用引用类型包装器才能使此解决方案发挥作用。