了解 AppDelegate 中的保留计数

Understanding Retain count in an AppDelegate

在应用程序中,我想知道为什么在退出应用程序时没有调用 class 的 deinit 方法的实例。

例如,此处显示的测试 class 是在 AppDelegate 的 applicationDidFinishLaunching.

中创建的
import Cocoa

class Test {
    let testVar = 1

    init() {

        print("Retain count \(CFGetRetainCount(self))")
        NSApplication.shared().terminate(self)
    }

    deinit {
        print("Calling deinit")
    }
}


@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    //@IBOutlet weak var window: NSWindow!    

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application

        _ = Test()  
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application            

        print("Terminating")
    }
}

这样不仅调用Test的deinit方法失败,而且Test的init中的retain count为2;我原以为这是 1.

如果一个可选的引用存储在AppDelegate class中并且在创建Test实例时设置,当applicationWillTerminate被调用时它是nil

有人可以解释一下为什么这里的保留计数是 2 以及如何确保在应用程序终止时调用 Test 的 deinit 吗?

我不能说为什么保留计数是 2。一般来说,启用 ARC,你真的不应该检查保留计数,原因是 well answered question

此外,有一个 answer 表明 CFGetRetainCount 在您调用它时实际上可能会增加保留计数。

在你的情况下,deinit 没有被调用,因为你在初始化程序完成之前以编程方式终止应用程序。

在将 Test 的实例分配给 AppDelegate 上的变量的情况下,不会调用 deinit,因为 AppDelegate 未在 "normal" 应用程序退出时的方式。因此,它的所有属性都不会被释放。如果您在 applicationWillTerminate 中将变量设置为 nil,您将看到 deinit 随后被适当地调用。在 this answer.

中谈论全局变量时解释了此行为

提供您所提供示例的特定排列:

class Test {
    let testVar = 1

    init() {
        print("Retain count \(CFGetRetainCount(self))")
    }

    deinit {
        print("Calling deinit")
    }
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var variable: Test?

    func applicationDidFinishLaunching(_ aNotification: Notification) {

        variable = Test()

        DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
            // If you're trying to pass in `variable`, the terminate funciton will retain it
            NSApplication.shared.terminate(nil)
        }
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        variable = nil
    }
} 

deinit 仅在实例由 ARC 释放时保证被调用,但如果从未调用释放,例如,如果应用程序崩溃或被用户强制执行,则不会被调用所以,不要依赖它绝对关键 "clean up".

返回保留计数。您的代码完全按照您的问题执行,产生以下内容:

我们看到的是保留计数在调用 CFGetRetainCount 期间递增 1,然后在调用 returns 时递减,然后在传递给 [=] 时再次递增22=].

我假设 Swift 的情况与 Objective-C 中记录的情况相同 link: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-BAJHFBGH

"When an application terminates, objects may not be sent a dealloc message. Because the process’s memory is automatically cleared on exit, it is more efficient simply to allow the operating system to clean up resources than to invoke all the memory management methods."

此问题是由于从 Test class 的 init 中终止应用程序所致。我怀疑在 init 中调用 terminate 会阻止 class 的正确实例化,因此它的 deinit 永远不会被调用。

通过延迟对 terminate 的调用,按预期调用对测试 deinit 的调用

import Cocoa

class Test {

    init() {

        DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
            NSApplication.shared().terminate(self)
        }
    }

    deinit {
        print ("Calling Deinit")
    }
}


@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    //@IBOutlet weak var window: NSWindow!
    var variable: Test?

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application

        variable = Test()

    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application

        variable = nil
        print("Terminating")
    }
}