保存闭包作为变量理解

时间:2015-02-07 16:55:00

标签: ios swift closures

我在游乐场测试此代码(我使用UnsafeMutablePointers来模拟取消初始化):

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()

但是,当我将TestClassB的初始化更改为:

p2.initialize(TestClassB(closure: {p1.memory.returnFive()}))

现在可以取消初始化TestClassA。

有人可以告诉我,

之间有什么区别
TestClassB(closure: p1.memory.returnFive)

TestClassB(closure: {p1.memory.returnFive()})

为什么在第二种情况下没有对TestClassA的强引用所以它可以被去除资本化?

2 个答案:

答案 0 :(得分:2)

这里的问题是使用UnsafeMutablePointer<SomeStruct>.memory。重要的是不要陷入这样的陷阱:memory就像包含指向对象的存储属性一样,只要指针存在就会保持活着状态。即使感觉像是一个,但它不是,它只是原始记忆。

这是一个只使用一个类的简化示例:

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()

但是,假设您获取了memory的值的副本,并将其分配给另一个变量。这样做会增加指向的对象memory的引用计数(就像你获取另一个常规类引用变量的副本一样:

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)

现在,假设您创建了一个捕获p的闭包,并在调用p.destroy()后使用了它的内存:

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()

但是,就像你的情况一样,如果你只是将p.memory.f分配给f,那就完全可以了:

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

那么f如何捕捉价值?

正如@rintaro指出的那样,Swift中的成员方法是curried函数。想象一下,没有成员方法。相反,只有常规函数和具有成员变量的结构。你怎么写相同的方法?你可能会这样做:

// 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更进一步,然后“curry”第一个参数,这样你就可以编写c.f并获得一个将f绑定到特定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

这相当于原始问题代码中的捕获,并且应该显示您没有看到原始A对象被销毁的原因。

顺便说一下,如果你真的想用一个闭包表达式来调用你的方法(假设你想在之前或之后做更多的工作),你可以使用变量捕获列表:

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

答案 1 :(得分:1)

p1.memory.returnFive中的

TestClassB(closure: p1.memory.returnFive)是一个与func returnFive() -> Int实例绑定的curry函数ClassA。它拥有对实例的引用。

另一方面,{p1.memory.returnFive()}只是一个捕获 p1 变量的闭包。此闭包没有引用ClassA本身的实例。

因此,在第二种情况下,p1.memoryClassA实例引用的唯一所有者。这就是p1.destroy()解除分配的原因。