Within an application, I'm wondering why an instance of a class's deinit method is not being called when quitting the application.
As an example, the Test class presented here is created in the AppDelegate's 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")
}
}
Not only does this fail to call Test's deinit method, but the retain count in Test's init is 2; I would have expected this to be 1.
If an optional reference is stored in the AppDelegate class and set when creating the Test instance, it is nil when applicationWillTerminate is called
Can someone please explain why the retain count is 2 here and how to ensure that deinit of Test is called when the application is terminated?
I assume the Swift situation is the same as the Objective-C one documented at this 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."
I can't speak to why the retain count is 2. In general, with ARC enabled, you really shouldn't ever inspect the retain count, and the reason why is a well answered question
Additionally, there is an answer that suggests CFGetRetainCount might actually be increasing the retain count when you call it.
In your situation, deinit is not being called because you're terminating the app programmatically before the initializer is finished.
In the situation of having an instance of Test assigned to a variable on your AppDelegate, deinit is not called because AppDelegate is not released in the "normal" way when an app exits. Therefore, all of it's properties are not released. If you set the variable to nil in applicationWillTerminate, you'll see that deinit is then called appropriately. The for this behavior is explained when talking about global variables in this answer.
To provide a specific permutation of your provided example:
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 is only guaranteed to be called if the instance is release by ARC, but if release is never called, for instance, if the app crashes or is force quite by the user it won't be So, don't rely on it for absolutely critical "clean up".
Circling back on the retain count. Your code, executed exactly as is in your question, produces the following:
What we're seeing is that the retain count being incremented by one during the call to CFGetRetainCount, then decremented when it returns, then incremented again once more when it's passed to terminate.
The issue is due to terminating the application from within the init of the Test class. I suspect that calling terminate in the init is preventing the correct instantiation of the class, so its deinit is never called.
By delaying the call to terminate, the call to Test's deinit is called, as expected
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")
}
}
Related
I am a Swift/MacOS newbie working on a MacOS application does some setup involving keychain access and API calls in the viewDidLoad() methods in the main ViewController.
I am working on unit tests for my models, so I don't need and in fact do not want the code in viewDidLoad() to run. However, from what I can tell, the app gets loaded and those methods run before the test case setup() method, so I don't know how I could do any mocking or other actions.
I am using Xcode 11.5 and Swift 5.
One way to do it would be to have an separate NSApplicationMain for when unit tests are run vs one for "normal" runs.
First remove the #NSApplicationMain annotation from your current AppDelegate class. It should end up looking something like this:
AppDelegate.swift
import AppKit
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("Debug/Production run")
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
Now create a new file called AppDelegateUnitTesting.swift and it's source should look like this:
AppDelegateUnitTesting.swift
import Foundation
import Cocoa
class AppDelegateTesting: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("Unit Testing Run")
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
Now add a new file called main.swift this file will determine in which environment our app is running, the source should be something like this:
main.swift
import Foundation
import Cocoa
let isRunningTests = NSClassFromString("XCTestCase") != nil &&
ProcessInfo.processInfo.arguments.contains("-XCUnitTests")
fileprivate var delegate: NSApplicationDelegate?
if !isRunningTests {
delegate = AppDelegate()
NSApplication.shared.delegate = delegate
// See this Answer to initialize the Windows programmatically
// https://stackoverflow.com/a/44604229/496351
} else {
delegate = AppDelegateTesting()
NSApplication.shared.delegate = delegate
}
NSApplication.shared.run()
To determine whether it's running in a Unit Test environment it checks if it can load the XCTestClass (which is only injected when testing) and it checks for the presence of the -XCUnitTest command line argument, we have to set this argument ourselves as part of the Scheme's Test action as shown in the image below
After doing all of this, you should see the message "Debug/Production run" printed when you press the play button and you should see the message "Unit Testing Run" printed whenever you run your unit tests.
You'll most likely have to add code to load the initial window programmatically this other answer shows how to do it:
how to Load initial window controller from storyboard?
I believe I understand what the dispatch queue is doing when I call it, but I'm not sure when exactly I should use it and what it's advantages are when I do use it.
If my understanding is correct, DispatchQueue.main.async { // code } will schedule the code contained within the closure to run on the main dispatch queue in an asynchronous manner. The main queue has the highest priority, and is typically reserved for updating UI to maximize App responsiveness.
Where I'm confused is: What exactly is the difference in updating UI elements within a dispatch queue closure versus just writing the code outside the closure in the same spot? Is it faster to execute the code in the body of a view did load method rather than sending it to the dispatch queue? If not, why?
Code Example:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
}
Versus:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
updateUI()
}
}
}
Which one is will update the UI faster?
The primary use of DispatchQueue.main.async is when you have code running on a background queue and you need a specific block of code to be executed on the main queue.
In your code, viewDidLoad is already running on the main queue so there is little reason to use DispatchQueue.main.async.
But isn't necessarily wrong to use it. But it does change the order of execution.
Example without:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
updateUI()
print("after")
}
}
As one might expect, the output will be:
before
update
after
Now add DispatchQueue.main.async:
class MyViewController: UIViewController {
func updateUI() {
print("update")
}
override func viewDidLoad() {
super.viewDidLoad()
print("before")
DispatchQueue.main.async {
updateUI()
}
print("after")
}
}
And the output changes:
before
after
update
This is because the async closure is queued up to run after the current runloop completes.
I just ran into the exact situation discribed in your Question: viewDidLoad() calling DispatchQueue.main.async.
In my case I was wanting to modify Storyboard defaults prior to displaying a view.
But when I ran the app, the default Storyboard items were momentarily displayed. The animated segue would finish. And only THEN would the UI components be modified via the code in viewDidLoad(). So there was this annoying flash of all of the default storyboard values before the real values were edited in.
This was because I was modifying those controls via a helper function that always first dispatched to the main thread. That dispatch was too late to modify the controls prior to their first display.
So: modify Storyboard UI in viewDidLoad() without dispatching to the Main Thread. If you're already on the main thread, do the work there. Otherwise your eventual async dispatch may be too late.
I need to read a checkbox’s state from a thread that is not the main thread. It seems I cannot simply use the following (which causes the Main Thread Checker to say “UI API called on a background thread: -[NSCell state]”):
myCheckbox.state
What am I supposed to do instead?
My current solution is to maintain a property that gets updated when the checkbox gets switched, and access that property instead of directly reading the checkbox’s state.
Something more elegant would be welcome: this is cumbersome; even more so if you want to make the property read-only or properly handle setting its value from inside the program.
class myViewController: NSViewController {
#IBOutlet private weak var myCheckbox: NSButtonCell!
public var myCheckboxState: Bool! // This can be read from any thread.
#IBAction func onMyCheckboxAction(_ sender: NSButtonCell) {
myCheckboxState = (myCheckbox.state == NSOnState)
}
override func viewDidLoad() {
super.viewDidLoad()
...
myCheckboxState = (myCheckbox.state == NSOnState)
...
}
...
}
I have a class object in the controller, and then I have a closure in this object.
I assign a function of the controller to the object's closure, and then the page does not deinit.
How can I solve this problem?
import UIKit
class SecondViewController: UIViewController {
let test = TestObject()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.test.select = self.selectButton(index:)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.test.doSomethine()
}
func selectButton(index:Int){
print(index)
}
deinit {
print("deinit")
}
}
import UIKit
typealias selectBtnBlock = (_ index:Int)->()
class TestObject: NSObject {
var select:selectBtnBlock?
func doSomethine(){
self.select!(1)
}
}
This is because your test object's select closure strongly captures your SecondViewController when you do the following:
self.test.select = self.selectButton(index:)
I recommend you do some reading about weak and strong types via Apple's Swift language reference. The "interesting phenomenon" you encountered is called a strong reference cycle.
Essentially, since Swift uses ARC as its memory management model, any object that is referenced by at least one other object else will be kept alive, and its memory not deallocated.
In your case, test has captured its parent SecondViewContoller via the line I mentioned. What that means is you have a situation like the following:
SecondViewController -> (owns) test // since its a member of the class
test -> (strongly captures) SecondViewController // via the assignment
This causes a strong reference cycle between the two, and does not allow ARC to deallocate either.
When it (ARC) tries to free up test, is knows that SecondViewController references it, so it can be freed only if the parent is also freed. When it tries to deallocate SecondViewController, ARC knows that this object is referenced by test.select closure.
Since both have a reference count greater than one, neither will get deallocated.
One way to solve your issue is to write:
self.test.select = {
[weak self] // weakly capture self, this prevents a ref cycle
(i:Int)->() in // a closure that accepts an Int
guard let s = self else { return } // check if self is not nil
s.selectButton(index: i) // finally invoke the required method
}
Another way, similar intent:
self.test.select = { [weak self] i in
self?.selectButton(index: i)
}
The weak keyword in this context is used to tell the Swift compiler that I do not want to keep a strong reference to what I am capturing (self in this case).
Trying to setup validation for a few text fields in a new (and very small) Swift Mac app. Following various other topics here on SO and a few other examples, I can still not get controlTextDidChange to propagate (to my ViewController).
E.g: How to live check a NSTextField - Swift OS X
I have read at least a dozen variations of basically that same concept. Since none of the accepted answers seem to work I am just getting more and more confused by something which is generally a fairly simple task on most platforms.
I have controlTextDidChange implemented to just call NSLog to let me know if I get anything.
AppDelegate should be part of the responder chain and should eventually handle controlTextDidChange but I see nothing there either.
Using the current Xcode I start a new project. Cocoa app, Swift, Storyboard and nothing else.
From what I can gather the below isolated example should work. In my actual app I have tried some ways of inserting the ViewController into the responder chain. Some answers I found suggested it was not always there. I also tried manually adding the ViewController as the delegate in code theTextField.delegate = self
Nothing I have done seems to get text changed to trigger any events.
Any ideas why I have so much trouble setting up this delegation?
My single textfield example app
Storyboard is about as simple as it gets:
AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTextFieldDelegate, NSTextDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("AppDelegate::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
ViewController
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate, NSTextDelegate {
#IBOutlet var theTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("ViewController::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
I think the samples you're following are a bit out-of-date.
Try...
override func controlTextDidChange(_ notification: Notification) {
...as the function definition for your method in your NSTextFieldDelegate.