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

时间:2017-01-30 16:38:14

标签: ios swift firebase swift3 firebase-realtime-database

我正在使用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不能隐式捕获变异的自身参数

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

5 个答案:

答案 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)副本,否则你无法以值类型捕获“变异自我”。

为什么不是Async?

这在异步上下文中不起作用的原因是:您仍然可以在没有编译器错误的情况下变更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是值类型,则需要使用引用类型包装器才能使此解决方案起作用。