【问题标题】:Swift: no output for println in deinit method (not using playground)Swift:在 deinit 方法中没有 println 的输出(不使用游乐场)
【发布时间】:2014-11-20 16:25:54
【问题描述】:

这是我的测试代码(在终端中运行):

#!/usr/bin/xcrun swift

var count = 0;  // for reference counting

class A {
    init() {
        count++;
    }
    deinit {
        println("A deinit")
        count--;
    }
}

var a: A? = A()
println(count)
a = nil  // no output if I comment out this statement
println(count)

输出:

1
A deinit
0

如果上面提到的行被注释掉,则没有输出“A deinit”。输出将是:

1
1

我已经使用swiftc 编译了代码,但结果还是一样。 (xcrun swiftc -o test test.swift)

程序退出时标准输出将被关闭,或者对象在被破坏时仍然被引用(通过什么?)是设计使然吗?

更新:感谢@Logan,现在我有了更多关于它的详细信息。

当它在函数内部运行时,即使我注释掉a = nil,它也会输出A deinit

#!/usr/bin/xcrun swift

class A {
    deinit {
        println("A deinit")
    }
}

func test() {
    var a: A? = A()
    //a = nil
}

test()

我没有在 Xcode 中使用游乐场。 :-$

更新

#!/usr/bin/xcrun swift

import Foundation

class A {
    deinit {
        var s = "A deinit"
        println(s)

        var a: A? = A()
        a = nil

        var error: NSError?
        var path = "\(NSFileManager.defaultManager().currentDirectoryPath)/swift_test.txt"
        if s.writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding, error: &error) {
            println("File saved at \(path)")
        } else {
            println(error)
        }
    }
}

//func test() {
    var a: A? = A()
//}
//test()

结果:没有输出到标准输出或文件,除非在 test 函数中运行。

【问题讨论】:

  • 到底是什么问题?为什么输出是1,第一种情况下是1,为什么在函数中deinited?
  • @Rob Napier 是对的,您也可以将变量 a 放在大括号中以使用函数来更改范围。

标签: macos swift xcode6


【解决方案1】:

虽然我没有看到明确的引用说“Swift 的 deinit 与 ObjC 的 dealloc 具有完全相同的语义”,但很难想象这不是真的,因为 Swift 和 ObjC 对象都是由 ARC 管理,一般来说可以互换。

鉴于此,这完全符合预期。 Cocoa 不会在程序终止时释放对象。它只是终止,泄漏所有内存、文件句柄和其他系统资源,并将其留给操作系统进行清理。这使得程序终止的速度比其他方式快得多。

这是很重要的一点,因为这意味着您通常不应该使用deinit 来管理操作系统管理的资源以外的任何东西(当然不是您需要运行的任何东西)。当然,没有办法保证析构函数运行,即使在 C++ 中也是如此。如果你崩溃了,它不会发生,你的程序将不得不处理它。你可以把它想象成所有 Cocoa 程序在它们终止时都会悄悄地崩溃。

因此,在您的情况下,a = nil 导致 deinit 运行,而程序终止则不会。

【讨论】:

  • 好意见!你的意思是只有Objective-C的C++中的析构函数也有这样的问题,还是任何C++的析构函数都有? (抱歉,我不小心按下了否决按钮,有什么影响吗?)
  • C++ 有析构函数。在程序终止期间,所有 C++ 析构函数都应该在程序终止之前执行。 ObjC 有释放器。终止期间不运行释放器。 Swift 有 deinitializers,它只是释放器的另一个名称。所以我只是说,即使在 C++ 中,应该 执行析构函数,你也无法知道 它们会执行。因此,您始终必须处理清理代码无法运行的可能性。
【解决方案2】:

程序正在退出,您想知道在早期的 Swift 实现中是否存在竞争(例如 Swift 实现错误)。退出处理程序绝对应该在正常程序退出时调用(而不是 SIGKILL)。

我建议在 deinit 中创建一个文件并在程序退出后检查它是否存在,但如果 I/O 通道如您所推测的那样过早关闭,那么这不是一个有用的测试。

那么 .. 尝试从 deinit 崩溃你的代码怎么样?

class B {
     nop() { }
}

class A {
     var b: B? = B()
     deinit {
         b = nil             
         b!.nop()
     }
} 

var a = A()

当 deinit 尝试通过 nil 引用访问函数时,程序应该以堆栈跟踪终止吗? (我假设)。

我可能会在某个时候尝试一下。目前我正在使用 Xcode,但从命令行尝试也会很有趣。

【讨论】:

  • 编译器会先检查潜在的错误,所以b!.nop()会编译失败。
  • 这里的解释是deinit 不是“退出处理程序”,因此不会运行。
  • @JakWings - 好的,我编辑了示例以定义 b,然后在 deinit 中将其设置为 nil。
  • @RobNapier - 谢谢!我不知道 Cocoa 和应用程序退出。感谢您对 deinit 与退出处理程序的说明。我猜我太 UNIXy 了 :-)
猜你喜欢
  • 2019-12-15
  • 2019-09-26
  • 2020-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-13
相关资源
最近更新 更多