I have a very simple code. I’m PURPOSELY creating a memory cycle with a delegate. Trying to observe and learn how to use Xcode's Memory Graph.
What I don’t get is why in the connections sections, Xcode says there are 3 connections. There should only be 2.
If I create a memory cycle with with closures, then it will show 2 connections.
My code for leaking with delegate:
protocol SomeDelegate {
func didFinishSomething()
}
class Something {
var delegate: SomeDelegate?
}
class ViewController: UIViewController, SomeDelegate {
var x = Something()
override func viewDidLoad() {
super.viewDidLoad()
print("view did load")
x.delegate = self
}
func didFinishSomething() {
print("something was finished")
}
deinit {
print("dellocated!!!")
}
}
I push that view into a navigationController then go back.
The 2 delegate objects have a slightly different memory addresses, it differs just by a +16
It seems that it has something to do with delegate object being a protocol. Because when I removed the protocol, then it reduced down to 2:
class Something2 {
var delegate: ViewController?
}
class ViewController: UIViewController {
var x = Something2()
override func viewDidLoad() {
super.viewDidLoad()
print("view did load")
x.delegate = self
}
deinit {
print("dellocated!!!")
}
}
I'm pretty sure that this is just Xcode getting confused. A protocol value shouldn't cause any extra retains over a normal strong reference, as memory management operations are forwarded to the underlying value through the type metadata stored within the existential container.
Here's a minimal example that reproduces the same result in Xcode's memory graph debugger:
If you insert a breakpoint between the print statements and look at the memory graph, you'll see 3 connections. Interestingly enough, if you assign
c.d
after you assignd.p
, you'll see the correct result of 2 connections instead.However if we set symbolic breakpoints on
swift_retain
andswift_release
in order to see the strong retain/release Swift ARC traffic (while printing out the value stored in the%rdi
register, which appears to be the register used to pass the argument on x86):and then insert a breakpoint immediately after the call to
foo()
, we can see that in both cases the instances each get +2 retained and -2 released (bearing in mind they enter the world as +1 retained, thus keeping them allocated):So it looks like Xcode is at fault here, not Swift.
Yes, the existential container that is used to implement protocol-typed variables can generate an extra retain. See Understanding Swift Performance for various implementation details. 16 bytes (2 machine words) is the header of the existential container.