Reputation: 12015
I'm testing in playground this code (I'm using UnsafeMutablePointers to simulate deinitialization):
class TestClassA {
func returnFive() -> Int {
return 5
}
deinit {
println("Object TestClassA is destroyed!") //this way deinit is not called
}
}
class TestClassB {
let closure: () -> Int
init(closure: () -> Int) {
self.closure = closure
}
deinit {
println("Object TestClassB is destroyed!")
}
}
let p1 = UnsafeMutablePointer<TestClassA>.alloc(1)
p1.initialize(TestClassA())
let p2 = UnsafeMutablePointer<TestClassB>.alloc(1)
p2.initialize(TestClassB(closure: p1.memory.returnFive))
p2.memory.closure()
p1.memory.returnFive()
p1.destroy()
However, when I change the initialization of TestClassB as:
p2.initialize(TestClassB(closure: {p1.memory.returnFive()}))
now TestClassA can be deinitialized.
So can someone tell me, what is the difference between
TestClassB(closure: p1.memory.returnFive)
and
TestClassB(closure: {p1.memory.returnFive()})
and why in the second case there is no strong reference to TestClassA so it can be deinitalized?
Upvotes: 2
Views: 126
Reputation: 40955
The problem here is the use of UnsafeMutablePointer<SomeStruct>.memory
. Its important not to fall into the trap of thinking that memory
is like a stored property containing the pointed-to object, that will be kept alive as long as the pointer is. Even though it feels like one, it isn’t, it’s just raw memory.
Here’s a simplified example that just uses one class:
class C {
var x: Int
func f() { println(x) }
init(_ x: Int) { self.x = x; println("Created") }
deinit { println("Destroyed") }
}
let p = UnsafeMutablePointer<C>.alloc(1)
p.initialize(C(42))
p.memory.f()
p.destroy() // “Destroyed” printed here
p.dealloc(1)
// using p.memory at this point is, of course, undefined and crashy...
p.memory.f()
However, suppose you took a copy of the value of memory
, and assigned it to another variable. Doing this would increment the reference count of the object memory
pointed to (same as if you took a copy of another regular class reference variable:
let p = UnsafeMutablePointer<C>.alloc(1)
p.initialize(C(42))
var c = p.memory
p.destroy() // Nothing will be printed here
p.dealloc(1)
// c has a reference
c.f()
// reassigning c decrements the last reference to the original
// c so the next line prints “Destroyed” (and “Created” for the new one)
c = C(123)
Now, imagine you created a closure that captured p
, and used it’s memory after p.destroy()
was called:
let p = UnsafeMutablePointer<C>.alloc(1)
p.initialize(C(42))
let f = { p.memory.f() }
p.destroy() // “Destroyed” printed here
p.dealloc(1)
// this amounts to calling p.memory.f() after it's destroyed,
// and so is accessing invalid memory and will crash...
f()
But, as in your case, if you instead just assign p.memory.f
to f
, it’s perfectly fine:
let p = UnsafeMutablePointer<C>.alloc(1)
p.initialize(C(42))
var f = p.memory.f
p.destroy() // Nothing will print, because
// f also has a reference to what p’s reference
// pointed to, so the object stays alive
p.dealloc(1)
// this is perfectly fine
f()
// This next line will print “Destroyed” - reassigning f means
// the reference f has to the object is decremented, hits zero,
// and the object is destroyed
f = { println("blah") }
So how come f
captures the value?
As @rintaro pointed out, member methods in Swift are curried functions. Imagine there were no member methods. Instead, there were only regular functions, and structs that had member variables. How could you write the equivalent of methods? You might do something like this:
// a C.f method equivalent. Using this
// because self is a Swift keyword...
func C_f(this: C) {
println(this.x)
}
let c = C(42)
// call c.f()
C_f(c) // prints 42
Swift takes this one step further though, and “curries” the first argument, so that you can write c.f
and get a function that binds f
to a specific instance of C
:
// C_f is a function that takes a C, and returns
// a function ()->() that captures the this argument:
func C_f(this: C) -> ()->() {
// here, because this is captured, it’s reference
// count will be incremented
return { println(this.x) }
}
let p = UnsafeMutablePointer<C>.alloc(1)
p.initialize(C(42))
var f = C_f(p.memory) // The equivalent of c.f
p.destroy() // Nothing will be destroyed
p.dealloc(1)
f = { println("blah") } // Here the C will be destroyed
This is equivalent to the capture in your original question code and should show why you aren’t seeing your original A object being destroyed.
By the way, if you really wanted to use a closure expression to call your method (supposed you wanted to do more work before or after), you could use a variable capture list:
let p = UnsafeMutablePointer<C>.alloc(1)
p.initialize(C(42))
// use variable capture list to capture p.memory
let f = { [c = p.memory] in c.f() }
p.destroy() // Nothing destroyed
p.dealloc(1)
f() // f has it’s own reference to the object
Upvotes: 2
Reputation: 51911
p1.memory.returnFive
in TestClassB(closure: p1.memory.returnFive)
is a curried function func returnFive() -> Int
bound to the instance of ClassA
. It owns the reference to the instance.
On the other hand, {p1.memory.returnFive()}
is just a closure that captures p1
variable. This closure does not have a reference to the instance of ClassA
itself.
So, in the second case, p1.memory
is the only owner of the reference to ClassA
instance. That's why p1.destroy()
deallocates it.
Upvotes: 1