ios 闭包不能隐式捕获变异的 self 参数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/41940994/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Closure cannot implicitly capture a mutating self parameter
提问by coding_999
I am using Firebase to observe event and then setting an image inside completion handler
我正在使用 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")!
}
})
However I am getting this error
但是我收到这个错误
Closure cannot implicitly capture a mutating self parameter
闭包不能隐式捕获变异的 self 参数
I am not sure what this error is about and searching for solutions hasn't helped
我不确定这个错误是关于什么的,寻找解决方案也没有帮助
回答by dfri
The short version
简短的版本
The type owning your call to FirebaseRef.observeSingleEvent(of:with:)
is most likely a value type (a struct
?), in which case a mutating context may not explicitly capture self
in an @escaping
closure.
拥有您调用的类型FirebaseRef.observeSingleEvent(of:with:)
很可能是值类型(a struct
?),在这种情况下,可能不会self
在@escaping
闭包中显式捕获变异上下文。
The simple solution is to update your owning type to a reference once (class
).
简单的解决方案是将您的拥有类型更新为一次引用 ( class
)。
The longer version
更长的版本
The observeSingleEvent(of:with:)
method of Firebaseis declared as follows
observeSingleEvent(of:with:)
Firebase的方法声明如下
func observeSingleEvent(of eventType: FIRDataEventType, with block: @escaping (FIRDataSnapshot) -> Void)
func observeSingleEvent(of eventType: FIRDataEventType, with block: @escaping (FIRDataSnapshot) -> Void)
The block
closure is marked with the @escaping
parameter attribute, which means it may escape the body of its function, and even the lifetime of self
(in your context). Using this knowledge, we construct a more minimal example which we may analyze:
所述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 */
}
}
Now, the error message becomes more telling, and we turn to the following evolution proposal was implemented in Swift 3:
现在,错误信息变得更有说服力,我们转向在 Swift 3 中实现的以下进化提议:
Stating [emphasis mine]:
说明[强调我的]:
Capturing an
inout
parameter, includingself
in a mutating method, becomes an error in an escapable closure literal, unless the capture is made explicit(and thereby immutable).
捕获一个
inout
参数,包括self
在一个 mutating method 中,在一个可转义的闭包文字中会变成一个错误,除非捕获是显式的(因此是不可变的)。
Now, this is a key point. For a valuetype (e.g. struct
), which I believe is also the case for the type that owns the call to observeSingleEvent(...)
in your example, such an explicit capture is not possible, afaik (since we are working with a value type, and not a reference one).
现在,这是一个关键点。对于值类型(例如struct
),我相信observeSingleEvent(...)
在您的示例中拥有调用的类型也是这种情况,这样的显式捕获是不可能的,afaik(因为我们正在使用值类型,而不是引用一)。
The simplest solution to this issue would be making the type owning the observeSingleEvent(...)
a reference type, e.g. a class
, rather than a struct
:
解决此问题的最简单方法是使类型拥有observeSingleEvent(...)
引用类型,例如 a class
,而不是 a struct
:
class Foo {
init() {}
private func bar(with block: @escaping () -> ()) { block() }
func bax() {
bar { print(self) }
}
}
Just beware that this will capture self
by a strong reference; depending on your context (I haven't used Firebase myself, so I wouldn't know), you might want to explicitly capture self
weakly, e.g.
请注意,这将self
被强引用捕获;根据您的上下文(我自己没有使用过 Firebase,所以我不知道),您可能想要明确地捕获self
弱,例如
FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...
回答by ctietze
Sync Solution
同步解决方案
If you need to mutate a value type (struct
) in a closure, that may only work synchronously, but not for async calls, if you write it like this:
如果你需要改变struct
闭包中的值类型 ( ),它可能只能同步工作,但不适用于异步调用,如果你这样写:
struct Banana {
var isPeeled = false
mutating func peel() {
var result = self
SomeService.synchronousClosure { foo in
result.isPeeled = foo.peelingSuccess
}
self = result
}
}
You cannot otherwise capture a "mutating self" with value types except by providing a mutable (hence var
) copy.
除非提供可变(因此var
)副本,否则您无法以其他方式捕获具有值类型的“变异自我” 。
Why not Async?
为什么不是异步?
The reason this does not work in async contexts is: you can still mutate result
without compiler error, but you cannot assign the mutated result back to self
. Still, there'll be no error, but self
will never change because the method (peel()
) exits before the closure is even dispatched.
这在异步上下文中不起作用的原因是:您仍然可以在result
没有编译器错误的情况下进行变异,但不能将变异结果分配回self
. 尽管如此,不会有错误,但self
永远不会改变,因为方法 ( peel()
) 甚至在闭包被分派之前就退出了。
To circumvent this, you may try to change your code to change the async call to synchronous execution by waiting for it to finish. While technically possible, this probably defeats the purpose of the async API you're interacting with, and you'd be better off changing your approach.
为了避免这种情况,您可以尝试更改代码以通过等待它完成来将异步调用更改为同步执行。虽然在技术上是可行的,但这可能会违背您正在与之交互的异步 API 的目的,您最好改变您的方法。
Changing struct
to class
is a technically sound option, but doesn't address the real problem. In our example, now being a class Banana
, its property can be changed asynchronously who-knows-when. That will cause trouble because it's hard to understand. You're better off writing an API handler outside the model itself and upon finished execution fetch and change the model object.Without more context, it is hard to give a fitting example. (I assume this is model code because self.img
is mutated in the OP's code.)
更改struct
到class
在技术上是声音选项,但并没有解决真正的问题。在我们的示例中,现在是 a class Banana
,它的属性可以异步更改,谁知道什么时候。这会造成麻烦,因为它很难理解。您最好在模型本身之外编写 API 处理程序,并在完成执行后获取和更改模型对象。没有更多的上下文,很难给出一个合适的例子。(我假设这是模型代码,因为self.img
它在 OP 的代码中发生了变异。)
Adding "async anti-corruption" objects may help
添加“异步反腐败”对象可能会有所帮助
I'm thinking about something among the lines of this:
我正在考虑以下内容:
- a
BananaNetworkRequestHandler
executes requests asynchronously and then reports the resultingBananaPeelingResult
back to aBananaStore
- The
BananaStore
then takes the appropriateBanana
from its inside by looking forpeelingResult.bananaID
- Having found an object with
banana.bananaID == peelingResult.bananaID
, it then setsbanana.isPeeled = peelingResult.isPeeled
, - finally replacing the original object with the mutated instance.
- a
BananaNetworkRequestHandler
异步执行请求,然后将结果报告BananaPeelingResult
给 aBananaStore
- 在
BananaStore
随后采取适当Banana
的寻找来自其内部peelingResult.bananaID
- 找到了一个对象
banana.bananaID == peelingResult.bananaID
,然后设置banana.isPeeled = peelingResult.isPeeled
, - 最后用变异的实例替换原始对象。
You see, from the quest to find a simple fix it can become quite involved easily, especially if the necessary changes include changing the architecture of the app.
您会看到,从寻求简单修复的过程中,它会变得非常容易,尤其是在必要的更改包括更改应用程序的架构时。
回答by Nikolay Suvandzhiev
If someone is stumbling upon this page (from search) and you are defining a protocol
/ protocol extension
, then it might help if you declare your protocol
as class bound. Like this:
如果有人偶然发现此页面(来自搜索)并且您正在定义protocol
/ protocol extension
,那么如果您将您的声明声明protocol
为class bound可能会有所帮助。像这样:
protocol MyProtocol: class {
...
}
回答by Must_Save_Jane
You can try this! I hope to help you.
你可以试试这个!我希望能帮助你。
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
}
回答by Gujamin
Another solution is to explicitly capture self (since in my case, I was in a mutating function of a protocol extension so I couldn't easily specify that this was a reference type).
另一个解决方案是显式捕获 self (因为在我的情况下,我处于协议扩展的变异函数中,因此我无法轻松指定这是一个引用类型)。
So instead of this:
所以而不是这个:
functionWithClosure(completion: { _ in
self.property = newValue
})
I have this:
我有这个:
var closureSelf = self
functionWithClosure(completion: { _ in
closureSelf.property = newValue
})
Which seems to have silenced the warning.
这似乎使警告静音。
Note this does not work for value types so if self is a value type you need to be using a reference type wrapper in order for this solution to work.
请注意,这不适用于值类型,因此如果 self 是值类型,则您需要使用引用类型包装器才能使此解决方案起作用。