数据结构和观察者模式中的内存访问冲突

3
我正在一个Struct模型对象上实现观察者设计模式。我的想法是将模型向下传递给一系列UIViewController,每个控制器修改它时,先前的控制器也会使用对象的更改进行更新。
我知道这个问题可以通过使用类而不是struct来解决,并直接通过引用修改对象,但我正在尝试了解更多关于使用structs的知识。
struct ModelObject {
    var data: Int = 0 {
        didSet {
            self.notify()
        }
    }
    private var observers = [ModelObserver]()

    mutating func attachObserver(_ observer: ModelObserver){
        self.observers.append(observer)
    }

    private func notify(){
        for observer in observers {
            observer.modelUpdated(self)
        }
    }
}

protocol ModelObserver {
    var observerID: Int { get }
    func modelUpdated(_ model: ModelObject)
}

class MyViewController : UIViewController, ModelObserver {
    var observerID: Int = 1
    var model = ModelObject()

    override func viewDidLoad() {
        self.model.attachObserver(self)
        self.model.data = 777
    }

    func modelUpdated(_ model: ModelObject) {
        print("received updated model")
        self.model = model //<-- problem code
    }
}

简单来说,我的模型对象通过调用notify()通知任何观察者data发生了更改。
我现在的问题是内存访问:当data被设置为777时,self.model变为独占访问状态,当它调用notify方法并最终调用modelUpdatedself.model = model时,我们会遇到一个错误:
Simultaneous accesses to 0x7fd8ee401168, but modification requires exclusive access.

我该如何解决这个内存访问问题?

为什么不直接使用Combine呢?你这里也有一个引用循环,但也许这是离题了。 - J. Doe
@J.Doe 什么是Combine?还有引用循环在哪里?Model是一个结构体而不是类,因此它不会传递引用。 - user339946
MyViewController 持有一个强引用的实例,该实例又持有 MyViewController 的实例(均为强引用),这构成了一个引用循环。这与类/结构体无关。哇,Combine 是一个新的神奇框架,非常适合你想要构建的东西,可以观看 WWDC 2019 视频学习! - J. Doe
@J.Doe 是的,我的 ModelObject 内观察者数组需要弱引用视图控制器,并且还需要一个删除函数。但是一次只解决一个问题。 - user339946
1个回答

3
如果你正在观察“一件事情”,那么这个“事情”就有一个身份。它是一件特定的事情,而不是观察数字4。它是一个值;它没有身份。每个4都和其他的4一样。结构体是值类型。它们没有身份。你不应该像观察Int(事实上在Swift中,Int是一个结构体)一样去观察它们。
每次将结构体传递给函数时,都会创建其副本。所以当你说`self.model = model`时,你是在说“制作一个model的副本,并将其分配给这个属性。”但是你仍然处于独占访问块中,因为每次修改结构体也会创建一个副本。
如果你想要观察ModelObject,那么ModelObject应该是一个引用类型,即类。然后你可以谈论“这个特定的ModelObject”,而不是“一个包含这些值并且与包含相同值的任何其他ModelObject无法区分的ModelObject”。

谢谢,除了身份之外,使用结构体和类时应考虑哪些其他因素? - user339946
详细解释请参见 https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes,重要的一点是“默认选择结构体”并不意味着“大多数应用级别的类型将是结构体”,而是在没有其他强制性因素时使用结构体。但通常还有其他强制性因素需要考虑。 - Rob Napier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接