如何在没有强大的参考周期的情况下将对象生命周期组合在一起?

时间:2017-10-10 17:25:28

标签: swift automatic-ref-counting

背景

我对Swift(来自C ++和C#)的背景相对较新,而且我正在努力设计一些我的数据模型类型和接口。 ARC似乎很棒并且使某些事情变得更容易,但我发现自己遇到了某些设计挑战。

问题

假设我们想要使用不同对象(类A,B和C)的混合来表示一些复杂的互连数据。例如,假设每个A对象应该具有关联的B对象和多个C对象。 A对象还必须响应其链接对象的更改(例如,当C对象发生更改时,关联的A必须更新其自己的内部状态并通知其B)。

由于ARC,我们不能简单地让这些对象彼此强烈引用,否则我们最终会有一个强大的引用周期(和内存泄漏)。

幸运的是,Swift为我们提供了weakunowned个引用,因此我们可以像这样建立我们的关系(虚线是弱引用):

Object relationships

这是可行的,但设置起来有些麻烦,特别是对于大量互连对象。如果我们使用第三方类(我们只能通过扩展修改),那就更麻烦了。

此外,如果主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实体都与彼此相关并相互依赖。

根据自动生成的类,我没有看到任何弱引用。这些托管对象如何在没有内存泄漏的情况下保持对彼此的引用?

3 个答案:

答案 0 :(得分:2)

我看到你可以使用两种可能的策略(可能还有其他策略):

1)接受强大的参考周期并明确拆除

2)通过使各种类能够根据需要动态实例化其引用的对象,使各个类自治。

对于#1,您可以要求呼叫代码呼叫一些"清理"打破强引用的方法(例如A,B,C ......类的方法,它们将强引用设置为nil)。或者,您可以创建一个容器类来实例化对象并保存强引用,然后让调用代码拥有该容器。

对于#2,各种类只需要一个分层的Strong->< -Weak关系和" children"对象会懒洋洋地重新实例化他们的父母"实例根据需要(类似于ManagedObjects上的CoreData错误)。

答案 1 :(得分:1)

核心数据是您想要如何解决此类问题的一个相当不错的示例。在Core Data中,有一个实际拥有所有对象的托管对象上下文。托管对象是代理,实际上可能是故障。

鉴于您的示例(作为托管对象的后备存储),您可以通过存储托管对象ID而不是对象本身来解决此问题。当你想要访问底层对象时,你会询问它的上下文,它会返回现有的objet或者存在一个缺陷。

如果您拥有自己的(非核心数据)系统,则可以使用NSCacheNSDiscardableContent创建类似的方法,以便在他们被主动使用时清除对象,并清除他们什么时候不是。您只需要一种机制,在需要时重新实例化它们。

但关键是使用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?
}

<强>通知

这是部分通知解决方案。所需的只是在BeeCee内发布更改通知。

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

键值观察是一个复杂的主题,如果没有所有细节,就不容易涵盖。