闭包无法隐式捕获变异的 self 参数

问题描述 投票:0回答:5

我正在使用 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 参数

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

ios swift firebase swift3 firebase-realtime-database
5个回答
106
投票

精简版

调用

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 ...

43
投票

同步解决方案

如果您需要在闭包中改变值类型(

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 代码中发生了变异。)

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

我正在思考其中的一些内容:

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

您会发现,从寻找简单修复的过程中,它可能会变得非常复杂,特别是如果必要的更改包括更改应用程序的架构的话。


16
投票

如果有人(通过搜索)偶然发现此页面,并且您正在定义

protocol
/
protocol extension
,那么如果您将
protocol
声明为 类绑定 可能会有所帮助。像这样:

protocol MyProtocol: AnyObject {
   ...
}

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
}

-18
投票

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

所以代替这个:

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

我有这个:

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

这似乎消除了警告。

请注意,这不适用于值类型,因此如果 self 是值类型,则需要使用引用类型包装器才能使此解决方案发挥作用。

© www.soinside.com 2019 - 2024. All rights reserved.