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

cod*_*999 51 ios firebase swift firebase-realtime-database swift3

我正在使用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")!
        }
})
Run Code Online (Sandbox Code Playgroud)

但是我收到了这个错误

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

我不确定这个错误是什么,搜索解决方案没有帮助

dfr*_*fri 71

简短的版本

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

简单的解决方案是将您拥有的类型更新为引用一次(class).


版本较长

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

func observeSingleEvent(of eventType: FIRDataEventType, 
     with block: @escaping (FIRDataSnapshot) -> Void)
Run Code Online (Sandbox Code Playgroud)

所述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              */
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,错误消息变得更有说服力,我们转向在Swift 3中实现了以下进化提议:

陈述[强调我的]:

捕获inout参数(包括self在变异方法中)会成为可逃避的闭包文字中的错误,除非捕获是显式的(从而是不可变的).

现在,这是一个关键点.对于类型(例如struct),我认为对于observeSingleEvent(...)在您的示例中拥有调用的类型也是如此,这样的显式捕获是不可能的,afaik(因为我们使用的是值类型,而不是引用一).

解决此问题的最简单方法是使类型拥有observeSingleEvent(...)引用类型,例如a class,而不是struct:

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

    func bax() {
        bar { print(self) }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...
Run Code Online (Sandbox Code Playgroud)

  • 最后一段很重要.小心捕获`@ escape`块中的`self`,可能会以保留周期结束,这可能导致内存泄漏.更好地利用`[弱自我]`(或`[无主的自我]`如果你知道有关块的生命周期)中的所有`@ escape`块捕捉`self`. (8认同)

cti*_*tze 18

同步解决方案

如果你需要改变一个struct闭包中的值type(),它可能只能同步工作,但不能用于异步调用,如果你这样写:

struct Banana {
    var isPeeled = false
    mutating func peel() {
        var result =  self

        SomeService.synchronousClosure { foo in
            result.isPeeled = foo.peelingSuccess
        }

        self = result
    }
}
Run Code Online (Sandbox Code Playgroud)

除非通过提供可变(因此var)副本,否则您无法以值类型捕获"变异自我" .

为什么不异步?

这在异步上下文中不起作用的原因是:您仍然可以在result没有编译器错误的情况下进行变异,但是您无法将变异结果分配回来self.仍然,没有错误,但self永远不会改变因为方法(peel())在调度闭包之前退出.

为了避免这种情况,您可以尝试更改代码,以通过等待它完成来将异步调用更改为同步执行.虽然技术上可行,但这可能会破坏您正在与之交互的异步API的目的,并且您最好改变您的方法.

更改structclass技术上合理的选项,但不解决实际问题.在我们的示例中,现在是a class Banana,其属性可以异步更改who-know-when.这会带来麻烦,因为它很难理解.您最好在模型本身之外编写API处理程序,并在完成执行获取并更改模型对象之后.没有更多的背景,很难给出一个合适的例子.(我假设这是模型代码,因为self.img在OP的代码中发生了变异.)

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

我正在考虑以下几点:

  • a BananaNetworkRequestHandler异步执行请求,然后将结果报告BananaPeelingResult给aBananaStore
  • BananaStore随后采取适当Banana的寻找来自其内部peelingResult.bananaID
  • 找到一个对象banana.bananaID == peelingResult.bananaID,然后设置banana.isPeeled = peelingResult.isPeeled,
  • 最后用变异的实例替换原始对象.

您可以看到,从寻找简单修复的过程中,它可以很容易地参与进来,特别是如果必要的更改包括更改应用程序的体系结构.

  • 这是一个出色的答案,应予以投票。 (2认同)

Nik*_*iev 13

如果有人绊倒了这个页面(来自搜索)并且你正在定义一个protocol/ protocol extension,那么如果你声明你protocol类绑定可能会有所帮助.像这样:

protocol MyProtocol: class {
   ...
}
Run Code Online (Sandbox Code Playgroud)