闭包不能隐式捕获变化的自身参数

我使用 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")!
}
})

然而我得到了这个错误

闭包不能隐式捕获变化的自身参数

我不确定这个错误是关于什么的,寻找解决方案也没有帮助

65405 次浏览

长话短说

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

简单的解决方案是将自己的类型更新为引用一次(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参数,< strong > 在突变中包含 self 方法 在可转义闭包文字 < strong > 中成为错误,除非 捕获被显式地设置为 (因此是不可变的)

现在,这是一个关键点。对于 价值类型(例如 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 ...

另一个解决方案是显式捕获 self (因为在我的例子中,我处于一个协议扩展的变异函数中,所以我不能轻易地指定这是一个引用类型)。

所以不是这样:

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

我有这个:

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

这似乎消除了警告。

注意,这对值类型不起作用,因此如果 self 是值类型,则需要使用引用类型包装器才能使该解决方案起作用。

如果有人偶然发现了这个页面(来自搜索) ,并且您正在定义 protocol/protocol extension,那么如果您将 protocol声明为 阶级界限,可能会有所帮助。像这样:

protocol MyProtocol: class {
...
}

同步解决方案

如果您需要在闭包中变更值类型(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,它的属性可以异步更改,谁知道什么时候。这会引起麻烦,因为这很难理解。没有更多的上下文,很难给出一个合适的例子。(我假设这是模型代码,因为 self.img在 OP 的代码中发生了变异。)

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

我在想一些事情:

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

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

你可以试试这个! 我希望能帮助你。

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
}