通过实例方法设置时的快速保留周期问题

时间:2019-02-18 14:30:11

标签: swift class retain-cycle

如果我这样创建一个类Agent。它对另一个Agent对象的引用很弱。

class Agent {

    weak var partner: Agent?
    var name: String

    init(name: String) {
        self.name = name
    }

    func makePartner(_ agent: Agent?) {
        partner = agent
        agent?.partner = self
    }

    deinit {
        print("Deinit for \(name)")
    }
}

var sam: Agent? = Agent(name: "Sam")

var bond: Agent? = Agent(name: "Bond")

//sam?.partner = bond //THIS WORKS
//bond?.partner = sam //THIS WORKS

bond?.makePartner(sam) //THIS DOESN'T WORK (deinit called for bond but not sam

sam = nil
bond = nil

如果我通过makePartner方法设置了伙伴关系,然后将两个对象都设置为nil,则只会调用bond的deinit而不是sam。

但是如果我使用

sam?.partner = bond //THIS WORKS
bond?.partner = sam //THIS WORKS

不是调用makePartner,而是调用两个deinit。你能解释为什么会这样吗?通过makePartner方法设置合作伙伴时,哪个参考仍保留给sam。

2 个答案:

答案 0 :(得分:0)

这是与Playground相关的问题。在上面的应用程序代码中工作正常。 如果您仍然想在操场上解决此问题, 您可以通过以下方式解决此问题:

bond?.makePartner(sam) - this line

包含以下几行:

bond?.partner = sam
sam?.partner = bond

答案 1 :(得分:0)

此处没有“强参考周期”(以前称为“保留周期”)。您的weak引用可以防止这种情况。

无法看到两个对象都已被释放的证据,这不是问题代码的结果。这只是一些特殊的游乐场行为。

如果您在应用程序中运行此程序,则可以正常运行。

有趣的是,当我在Xcode 10.2 beta 2游乐场进行测试时,它在那儿也表现正常。


撇开这种分配问题,makePartner存在两个问题。我敢打赌,您不在乎,这只是对关系薄弱的考验,但如果您确实在意,我想澄清一下问题:

  • 如果“ A”是“ B”的合伙人,但现在我们希望使其成为“ C”的合伙人怎么办。您的代码将使“ A”和“ C”成为彼此的合作伙伴,但是“ B”仍会悬而未决,尽管它与“ A”仍是合作伙伴。

  • 或者如果“ C”以前是“ D”的伙伴,现在又被重新分配为“ A”,那我们真的需要让“ D”知道它不再是“ C”的伙伴。

  • 或者假设“ A”是“ B”的合作伙伴,现在我们要说它没有合作伙伴,即其合作伙伴是nil。同样,我们需要让“ B”知道其伙伴也是nil,而不是“ A”。

  • 最后,如您所见,这种“一个人只能与另一个人成为伙伴”这种双重链接的结构有点脆弱,我们真的想确保没有外部代码可以改变任何人的伙伴,但只能通过makePartner来完成。

因此,您可能会执行以下操作:

class Agent {
    weak private(set) var partner: Agent?                     // give this private setting so no one can mess with this fragile set of relationships

    let name: String

    init(name: String) {
        self.name = name
    }

    func makePartner(with newPartner: Agent?) {               // A was partners with B, but should now be partners with C ...
        let oldPartner = self.partner

        if let newPartnersOldPartner = newPartner?.partner {  // if C is currently partners with D ...
            newPartnersOldPartner.partner = nil               // ... then D is no longer partnered with anyone.
        }

        oldPartner?.partner = nil                             // nor is B any longer partners with anyone.
        newPartner?.partner = self                            // but C is now partners with A ...
        partner = newPartner                                  // ... and A is partners with C.
    }

    deinit {
        print("Deinit for \(name)")
    }
}

extension Agent: CustomStringConvertible {
    var description: String {                                 // give ourselves a nice, pretty description
        if let partner = partner {
            return "Agent \(name), who has partner \(partner.name)"
        } else {
            return "Agent \(name), who has no partner"
        }
    }
}

然后

var a = Agent(name: "A")
var b = Agent(name: "B")
a.makePartner(with: b)
var c = Agent(name: "C")
var d = Agent(name: "D")
c.makePartner(with: d)
print(a, b, c, d)
  

代理商A,而合伙人B
  经纪人B,有合伙人A
  代理商C,合伙人D
  代理D,他有合伙人C

然后

a.makePartner(with: c)
print(a, b, c, d)
  

A代理人(拥有C合伙人)
  代理商B,没有合伙人
  代理C,拥有合伙人A
  代理商D,没有合伙人

a.makePartner(with: nil)
print(a, b, c, d)
  

A代理人,没有合作伙伴
  代理商B,没有合伙人
  代理商C,没有合伙人
  代理商D,没有合伙人