Swift 通过实例方法设置时保留周期问题

Swift Retain Cycle Issue when setting via instance method

如果我像这样创建一个 class 代理。它持有对另一个 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。

这是一个 Playground 相关的问题。在上面的应用程序代码中工作得很好。 如果你还想在操场上解决这个问题, 您可以通过替换来修复此行为:

bond?.makePartner(sam) - this line

这些行:

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

这里没有“强引用循环”(以前称为“保留循环”)。您的 weak 引用阻止了这种情况。

未能看到两个对象被释放的证据是不是您问题中的代码的结果。这只是一些特殊的游乐场行为。

如果您 运行 在应用程序中使用它,它可以正常工作。

而且,有趣的是,当我在 Xcode 10.2 beta 2 playground 中测试它时,它在那里也表现正确。


抛开这个重新分配的问题不谈,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)

Agent A, who has partner B
Agent B, who has partner A
Agent C, who has partner D
Agent D, who has partner C

然后

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

Agent A, who has partner C
Agent B, who has no partner
Agent C, who has partner A
Agent D, who has no partner

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

Agent A, who has no partner
Agent B, who has no partner
Agent C, who has no partner
Agent D, who has no partner