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
如果我像这样创建一个 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