不应发生的 Swift 内存冲突
Posted
技术标签:
【中文标题】不应发生的 Swift 内存冲突【英文标题】:Swift memory conflict where it should not happen 【发布时间】:2021-04-23 16:08:53 【问题描述】:我正在开发一个使用 MVVM 架构的 SwiftUI 项目。
从 SwiftUI 视图更改 View-model 对象属性时,会导致 view-model 对象中的内存冲突崩溃。
错误类型:同时访问0x600003591b48,但修改需要独占访问。
在步骤中,会发生以下情况:
-
视图模型属性已从视图更改
视图模型属性更改模型属性
模型属性通知更改
视图模型收到更改通知
视图模型访问模型对象
由于内存冲突而发生崩溃
相关代码sn-ps见下。 Xcode 项目是一个标准的 SwiftUI 项目。
会出现错误,先点击添加按钮,再点击修改按钮。
如果将“update”代码移到“receiveValue”闭包中,则不会发生错误。同样,如果将 View-model 类设为非泛型,则不会发生错误。
据我所知,代码没问题,所以我怀疑这是编译器问题。但我不确定。
import Foundation
import SwiftUI
import Combine
struct ContentView: View
@ObservedObject var item: ViewModel<Model> = ViewModel<Model>()
var body: some View
VStack
Button("Add", action: item.add(model:Model()) )
Button("Modify", action: item.selected.toggle() )
protocol ModelType
var objectDidChange: ObservableObjectPublisher get
var selected: Bool get set
class Model: ModelType
let objectDidChange = ObservableObjectPublisher()
var selected = false
didSet
objectDidChange.send()
class ViewModel<Model:ModelType>: ObservableObject
var selected = false
didSet
model.selected = selected
func add(model: Model)
self.model = model
cancellable = model.objectDidChange.sink(receiveValue: _ in
self.update()
)
private var model: Model! = nil
private var cancellable: AnyCancellable? = nil
func update()
// Crash log: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
print("update \(model.selected)")
【问题讨论】:
你为什么不使用 ObservableObject 协议(和@Published)? 好问题?我做到了。作为简化的一部分,我将其删除,以查看它是否会改变有关错误的某些内容。它没有改变任何东西,我也没有把原来的代码放回去。 是的;这就是我删除 cmets 的原因。我想到了。我只是在写。 【参考方案1】:短版:AnyObject
需要 ModelType
。
长版:
在设置self.model
的过程中,您正尝试从self.model
读取数据。当您说“如果将“更新”代码移动到“receiveValue”闭包中,则不会发生错误”,这并不完全正确。我猜你的意思是你写了这个:
cancellable = model.objectDidChange.sink(receiveValue: _ in
print("update \(model.selected)")
)
这行得通,但那是完全不同的代码。 model
在这种情况下是局部变量,而不是属性 self.model
。如果你这样写,你会遇到同样的崩溃:
cancellable = model.objectDidChange.sink(receiveValue: _ in
print("update \(self.model.selected)")
)
带你到这里的路径是:
ViewModel.selected.didSet WRITE to Model.selected Model.selected.didSet (观察者关闭) ViewModel.update 从 ViewModel.model 读取这是对相同值的读取和写入,违反了独占访问。请注意,有问题的“值”是“整个 ViewModel 值”,而不是 ViewModel.selected。您可以通过将update
函数更改为:
print("update \(model!)")
你会遇到同样的崩溃。
那么,当您取出通用型时,为什么这会起作用?因为这种特别严格的排他性版本仅适用于值类型(如结构)。它不适用于课程。所以当这是具体的时候,Swift 知道 viewModel 是一个类,这没关系。 (这种差异背后的原因有点复杂,所以我建议阅读解释它的proposal。)
当你使这个通用化时,Swift 必须非常谨慎。它不知道 Model 是一个类,所以它应用了更严格的规则。你可以通过承诺它是一个类来解决这个问题:
protocol ModelType: AnyObject ...
【讨论】:
感谢您的回答。我怀疑这是问题所在,首先,我确实尝试像这样限制泛型。我是通过以下方式完成的: class ViewModel: ObservableObject 不幸的是,这与直接在协议上设置约束没有相同的效果。 我将状态更改回未答复,因为我仍然无法找到适合我的解决方案。问题是使协议继承自 AnyObject 只能部分解决问题。如果添加更多继承,问题仍然存在。例如,如果协议的构造如下:protocol ModelType: AnyObject, ProtocolA, ProtocolB
我无法重现后一个示例(ModelType 需要 AnyObject 和其他两个协议)。您需要发布演示这一点的代码。 (我可以重现 Model: AnyObject & ModelType
崩溃,我建议您打开一个 Swift 错误。这应该可以工作,即使我有点怀疑需要这么多协议和层;您可能使您的设计过于复杂。即使所以,它应该工作。)
如果将'objectDidChange'发布者放入ProtocolA,并且将'selected'布尔属性,加上新的description()函数放入ProtocolB,则会发生崩溃。但是,我注意到它不会崩溃的组合。最后,我无法避免在我的原始项目中有很多协议,因此,我现在使用一种 hack 来防止视图模型属性导致回调到视图模型模型对象中。我认为所有这些都是一个应该报告并希望有一天能解决的错误(在我的原始项目中,约束协议继承自 7-9 个次要协议)。
再次改变了我的想法。原始帖子没有将泛型限制为一个类,因此答案是可以的。另外,我会将问题报告为 Swift 中的错误。以上是关于不应发生的 Swift 内存冲突的主要内容,如果未能解决你的问题,请参考以下文章