Swift: no output for println in deinit method (not

2019-07-04 05:32发布

问题:

This is my test code (run in the terminal):

#!/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)

Output:

1
A deinit
0

There is no output "A deinit" if the line mentioned above is commented out. And the output will be:

1
1

I've used swiftc to compile the code but the result is still the same. (xcrun swiftc -o test test.swift)

Is it by design that the stdout will be closed when the program exits, or the objects are still referred (by what?) when they are destructed?

Update: Thanks to @Logan , now I have more details about it.

When it is run inside a function, it will output A deinit even if I comment out a = nil:

#!/usr/bin/xcrun swift

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

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

test()

I'm not using a playground in Xcode. :-$

Update

#!/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()

Result: No output to stdout or the file, unless running in the test function.

回答1:

While I don't see an explicit reference that says "Swift's deinit has exactly the same semantics as ObjC's dealloc," it is hard to imagine that this isn't true, since Swift and ObjC objects are both managed by ARC and generally speaking interchangeable.

Given that, this is exactly expected. Cocoa does not deallocate objects at program termination. It just terminates, leaks all the memory, file handles, and other system resources, and leaves it to the OS to cleanup. This makes program termination dramatically faster than it would be otherwise.

This is an important point because it means you generally should not use deinit to manage anything other than OS-managed resources (certainly not anything that you require to run). Of course, there's no way to guarantee that a destructor runs, even in C++. If you crash, it won't happen, and your program would have to deal with that. You can think of it as all Cocoa programs quietly crashing when they terminate.

So in your case, a = nil is causing deinit to be run, while program termination does not.



回答2:

The program is exiting and you wonder if there's a race (e.g. Swift implementation bug) in the early Swift implementation. Exit handlers should definitely be called at normal program exit (as opposed to SIGKILL).

I'd suggest creating a file in deinit and checking for its presence after the program exits, but if I/O channels are getting closed prematurely as you speculated, that isn't a useful test.

So .. how about trying to crash your code from deinit instead?

class B {
     nop() { }
}

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

var a = A()

When deinit trys to access a function through a nil reference, the program should terminate with a stack trace? (I assume).

I might experiment with this at some point. Currently I'm using Xcode but it would be fun to try from command line too.