使用deinit和defer在Swift

时间:2016-07-05 16:16:59

标签: swift memory-management

我正在学习更多关于Swift的知识并且最近遇到了defer声明,这对我来说似乎很有趣。但是我真的不明白它的目的。来自C ++我会使用deallocation函数实现相同的功能,事实上,由于Swift是ARC,它可以做同样的事情。

假设FooDataBarData都处理需要解除分配的数据。

class FooData {
    deinit {
        print("FooData being deallocated")
    }
}

class BarData {
}

func baz() -> Int {
    var a = FooData()
    var b = BarData()
    defer { print("BarData being deallocated") }

    /* sensitive operations that could throw at any time */

    return 0
}

baz()
// BarData being deallocated
// FooData being deallocated

那么defer方法优于deinit方法的优势是什么?只考虑使用defer除了资源清理以外的任何事情都让我受伤了......

5 个答案:

答案 0 :(得分:3)

你看到的不同但是没有,80:8080被Apple引入作为一种安全简便的方法来处理返回前的清理,但defer仅适用于范围。所以让我更好地解释一下,如果你在函数中定义了一些范围,你创建的变量只存在于你无法从defer访问的范围内,例如:

deinit

在这种情况下,将变量func resizeImage(url: NSURL) -> UIImage? { // ... let dataSize: Int = ... let destData = UnsafeMutablePointer<UInt8>.alloc(dataSize) defer { destData.dealloc(dataSize) } var destBuffer = vImage_Buffer(data: destData, ...) // scale the image from sourceBuffer to destBuffer var error = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, ...) guard error == kvImageNoError else { return nil } // create a CGImage from the destBuffer guard let destCGImage = vImageCreateCGImageFromBuffer(&destBuffer, &format, ...) else { return nil } // ... } 定义为全局变量没有意义,我们需要在完成工作后解除分配,因此destData它是选择

我认为defer它可以用于更全局的范围,例如当您使用deinit或其他您需要的东西实现键值观察器时。

我希望这对你有所帮助。

答案 1 :(得分:2)

在编程中,某些功能总是成对出现。例如,打开连接并关闭该连接,锁定互斥锁并解锁互斥锁,递增计数器,递减计数器,分配内存,释放内存。

模式通常如下所示:

lock()
... do something ...
unlock()

中间部分可能很复杂而且很长。可以有返回(例如,对于失败的前提条件,Swift推荐此模式及其guard)。有时很难不忘记在所有执行路径中包含unlock()

很好地解决这种情况的一种方法是使用辅助函数:

func doSomething() {
   ... do something with returns ...
}

lock()
doSomething()
unlock()

但这并非总是可行,例如当你有几个嵌套对象时。

在C中,通常使用goto来解决相同的模式:

x = malloc(...);
y = malloc(...);
if (!precondition) {
   goto end;
}

... some code ...

end:
free(y);
free(x);

现代语言带来了更好的方法,在Swift中使用defer实现(例如,你也可以在Go中找到defer。)

lock()
defer {
   unlock()
}
... some code ...

这种方法有几个好处:

  1. 你可以一起进行通话,这会增加可读性,并且很难忘记第二次通话。
  2. 所有返回,前置条件检查,错误处理都会使代码保持正确状态,因为unlock将始终被正确调用。这类似于Java(和其他语言)中的异常处理中的finally
  3. 如果您询问与deinit的区别,它的工作方式类似。但是defer可以在函数中工作,而deinit仅适用于类。

    另请注意,您可以使用defer重新实现deinit,但使用情况会更复杂,行为也更难以预测。

答案 2 :(得分:1)

defer可以被有条件地称为deinit无法实现的东西

var i = 1
func foo()->Int {
    if i == 1 {
        defer {
            i = 0
        }
    }
    return i + 1
}
print("foo:", foo(), "i:", i)

答案 3 :(得分:1)

在方法中使用延迟意味着它的工作将在方法退出时执行。

override func viewDidLoad() {
    super.viewDidLoad()

    print("Step 1")
    myFunc()
    print("Step 5")
}

func myFunc() {
    print("Step 2")
    defer { print("Step 3") }
    print("Step 4")
}

“步骤1”,“步骤2”,“步骤4”,“步骤3”,“步骤5” - 步骤3和4被切换,因为3被推迟到myFunc()方法结束,即它何时结束以编程方式超出范围。

关于 deinit ,这用于在取消初始化之前运行代码。 deinit代码自动运行。在实例解除分配之前,会自动调用取消初始化程序。您不能自己致电deinitializer。

class Item {
    init() {
    print("init called")
    }
    deinit {
    // Called whenever class stops existing.
    print("deinit called")
    }
}

// Create optional Item variable.
var i: Item? = Item()
// Set optional to nil to force deinit.
i = nil

答案 4 :(得分:0)

考虑数据库事务。您希望在完成后关闭连接,但是您希望保留对象以便将来恢复连接:

stuct foo {

    let db = Database()

    func useDatabase() throws {
        let connection = db.openConnection()
        defer {
            // close conenction, but keep db around for future use
            connection.close
        }

        try connection.thisCanThrow()
    }
}

这只是一个例子,但有许多人喜欢它。特别是,在很多情况下,您希望对状态的受限制生命周期进行建模,而不是将其与对象的生命周期紧密绑定。

C ++严重依赖RAII。斯威夫特当然有能力坚持相同的范式,但它也可以超越defer