1

If I create a class Agent like this. which holds a weak reference to another Agent object.

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

If I set partnership via makePartner method and then set both object to nil then only bond's deinit gets called and not for sam.

But if i use

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

instead of calling makePartner then both deinit gets called. can you explain why this is happening? which reference is remaining to sam while setting partner via makePartner method.

2 Answers2

0

It is a Playground related issue. In the app code above works just fine. If you still want to solve this issue in the playground, you can fix this behavior by replacing:

bond?.makePartner(sam) - this line

with these lines:

bond?.partner = sam
sam?.partner = bond
elisar4
  • 180
  • 1
  • 7
  • It has to be done with function not like 2 lines... when swift copies reference it goes to method local variable agent but when method completes it will destroy that reference so reference counter should decrease. – Ralph Korvin Feb 18 '19 at 14:52
  • try to use in-out parameter – elisar4 Feb 18 '19 at 14:54
  • did using inout still only one deinit called – Ralph Korvin Feb 18 '19 at 14:55
  • yeah, strange. seems like when you are assigning optional var it is increasing the reference count of a local variable (while unwrapping it?). and you cannot clear it even if you call .partner = nil for both objects... maybe it is a bug. still, the two-line solution works as expected. – elisar4 Feb 18 '19 at 15:22
  • 1
    I've tried to run this code in the real app (in AppDelegate) and it is working fine. Both agents deinitialize. So it is a Playground issue. – elisar4 Feb 18 '19 at 15:26
  • OK let me check in App again. – Ralph Korvin Feb 18 '19 at 15:30
  • yeah its working in app. sorry i am just still in learning phase. – Ralph Korvin Feb 18 '19 at 15:33
0

There is no “strong reference cycle” (formerly known a “retain cycle”) here. Your weak references prevent that.

The failure to see evidence of both objects being deallocated is not a result of the code in your question. It’s merely some idiosyncratic playground behavior.

If you run this in an app, it works fine.

And, interestingly, when I tested this in Xcode 10.2 beta 2 playground, it behaved correctly there, too.


Setting aside this deallocating concern, there are a couple of problems with makePartner. I’d bet you don’t care, that this was merely a test of weak relationships, but in case you do care, I’d like to clarify the issues:

  • What if “A” was partners with “B”, but we now want to make it partners with “C”. Your code will make “A” and “C” partners to each other, but “B” will still be dangling out there, still thinking it’s partners with “A”, even though it’s not.

  • Or what if “C” was previously partners with “D”, now that it’s been reassigned to “A”, we really need to let “D” know it’s no longer partners with “C”.

  • Or let’s assume “A” was partners with “B”, and we now want to say that it has no partner, i.e., that its partner is nil. Again, we need to let “B” know that its partner is also nil, not “A”.

  • Finally, as you can see, this “one person can only be partners with one other person” sort of doubly linked structure is kind of fragile, we really want to make sure that no external code can change anyone’s partner, but rather can only do it through makePartner.

So, you might do something like:

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"
        }
    }
}

Then

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

Then

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

And

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

Rob
  • 415,655
  • 72
  • 787
  • 1,044