我正在学习更多关于Swift的知识并且最近遇到了defer
声明,这对我来说似乎很有趣。但是我真的不明白它的目的。来自C ++我会使用deallocation函数实现相同的功能,事实上,由于Swift是ARC,它可以做同样的事情。
假设FooData
和BarData
都处理需要解除分配的数据。
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
除了资源清理以外的任何事情都让我受伤了......
答案 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 ...
这种方法有几个好处:
unlock
将始终被正确调用。这类似于Java(和其他语言)中的异常处理中的finally
如果您询问与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
。