背景
我对Swift(来自C ++和C#)的背景相对较新,而且我正在努力设计一些我的数据模型类型和接口。 ARC似乎很棒并且使某些事情变得更容易,但我发现自己遇到了某些设计挑战。
问题
假设我们想要使用不同对象(类A,B和C)的混合来表示一些复杂的互连数据。例如,假设每个A对象应该具有关联的B对象和多个C对象。 A对象还必须响应其链接对象的更改(例如,当C对象发生更改时,关联的A必须更新其自己的内部状态并通知其B)。
由于ARC,我们不能简单地让这些对象彼此强烈引用,否则我们最终会有一个强大的引用周期(和内存泄漏)。
幸运的是,Swift为我们提供了weak
和unowned
个引用,因此我们可以像这样建立我们的关系(虚线是弱引用):
这是可行的,但设置起来有些麻烦,特别是对于大量互连对象。如果我们使用第三方类(我们只能通过扩展修改),那就更麻烦了。
此外,如果主A对象仅使用弱引用,则它实际上无法使其关联的B或C对象保持活动状态。
扭转我们的weak
引用的方向将解决该特定问题,但它引入了它自己的缺点。
例如,假设视图或控制器只需要与C对象接口。但是,如果它仅维护对这些C对象的引用,并且这些C对象仅具有对后备A对象的弱引用,则可以释放该对象。这基本上强制客户端代码保持对A对象的引用,以防止它关心的C对象被孤立。
一厢情愿
理想情况下,我希望简单轻松地将这些对象的生命周期联系在一起,但不会产生内存泄漏。换句话说,我想创建一个A对象(然后创建自己的关联B和C对象)并让它们彼此保持活着,但只要外部代码至少有一个引用这些“捆绑”对象中的任何一个。
这在Swift中是否可行(和实用)?
更多细节
我不确定细节是否会澄清或弄清楚事情,但如果它有所帮助......我遇到的一个场景是建模数据并将其与Core Data一起存储。
例如,A是“公共”程序员友好界面(符合标准化数据协议),B是“私有”后备存储(核心数据实体),C是复杂数据的有用封装。
还有一些相关的D&我遗漏的E类也与主A对象相关联。这些本身就像我们的A类,即。具有自己复杂的多对象关系的数据模型的公共接口,但从根本上遇到与A到C关系相同的问题。
换句话说,客户端代码可能只需要与D接口,但是除非关联的A对象以某种方式保持活动状态,否则单独修改D将无法正常工作。
有点切线
这让我想知道Core Data实体如何管理他们的关系而不引入强大的参考周期。例如,您可能有X,Y和Z实体都与彼此相关并相互依赖。
根据自动生成的类,我没有看到任何弱引用。这些托管对象如何在没有内存泄漏的情况下保持对彼此的引用?
答案 0 :(得分:2)
我看到你可以使用两种可能的策略(可能还有其他策略):
1)接受强大的参考周期并明确拆除
2)通过使各种类能够根据需要动态实例化其引用的对象,使各个类自治。
对于#1,您可以要求呼叫代码呼叫一些"清理"打破强引用的方法(例如A,B,C ......类的方法,它们将强引用设置为nil)。或者,您可以创建一个容器类来实例化对象并保存强引用,然后让调用代码拥有该容器。
对于#2,各种类只需要一个分层的Strong->< -Weak关系和" children"对象会懒洋洋地重新实例化他们的父母"实例根据需要(类似于ManagedObjects上的CoreData错误)。
答案 1 :(得分:1)
核心数据是您想要如何解决此类问题的一个相当不错的示例。在Core Data中,有一个实际拥有所有对象的托管对象上下文。托管对象是代理,实际上可能是故障。
鉴于您的示例(作为托管对象的后备存储),您可以通过存储托管对象ID而不是对象本身来解决此问题。当你想要访问底层对象时,你会询问它的上下文,它会返回现有的objet或者存在一个缺陷。
如果您拥有自己的(非核心数据)系统,则可以使用NSCache
和NSDiscardableContent
创建类似的方法,以便在他们被主动使用时清除对象,并清除他们什么时候不是。您只需要一种机制,在需要时重新实例化它们。
但关键是使用ID或代理而不是实际对象,并保留管理内存的容器中的实际对象,就像Core Data一样。
答案 2 :(得分:0)
我正在阅读您的问题,并且我键入了以下短语,“A对象还必须响应其链接对象中的更改”。
在Swift中,属性通常用于显示所有权。
class A {
let bee: Bee = Bee()
let cees: [Cee] = [Cee(), Cee()]
}
class Bee {
}
class Cee {
}
实例间通信可以通过几种方式完成。
<强>团强>
class A: BeeDelegate, CeeDelegate {
let bee: Bee = Bee()
let cees: [Cee] = [Cee(), Cee()]
func beeDidChange(_ bee: Bee) {
}
func ceeDidChange(_ cee: Cee) {
}
init() {
bee.delegate = self
cees.forEach { $0.delegate = self }
}
}
protocol BeeDelegate {
func beeDidChange(_ bee: Bee)
}
class Bee {
weak var delegate: BeeDelegate?
}
protocol CeeDelegate {
func ceeDidChange(_ cee: Cee)
}
class Cee {
weak var delegate: CeeDelegate?
}
<强>通知强>
这是部分通知解决方案。所需的只是在Bee
和Cee
内发布更改通知。
class A: BeeDelegate, CeeDelegate {
let bee: Bee = Bee()
let cees: [Cee] = [Cee(), Cee()]
func beeDidChange(_ notification: Notification) {
}
func ceeDidChange(_ notification: Notification) {
}
var center = NotificationCenter.default
init() {
center.addObserver(forName: .beeDidChange, object: nil, queue: nil, using: beeDidChange)
center.addObserver(forName: .ceeDidChange, object: nil, queue: nil, using: beeDidChange)
}
deinit() {
center.removeObserver(self)
}
}
extension Notification.Name {
static let beeDidChange = Notification.Name(rawValue:"BeeDidChange")
}
extension Notification.Name {
static let ceeDidChange = Notification.Name(rawValue:"CeeDidChange")
}
<强> KVO 强>
键值观察是一个复杂的主题,如果没有所有细节,就不容易涵盖。