Dániel Nagy
Dániel Nagy

Reputation: 12015

Saving closure as a variable understanding

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

Answers (2)

Airspeed Velocity
Airspeed Velocity

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

rintaro
rintaro

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

Related Questions