I've created new app from macOS Cocoa App template. The only change I've made is added deinit in NSViewController. So now it looks like this(complete code):
import Cocoa
class ViewController: NSViewController {
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 doSomething()
{
var a = 10
a = a + 1
print(a)
}
deinit {
doSomething()
print("deinit called")
}
}
Why I don't see deinit call? I've searched number of questions here, but couldn't find answer, as I don't have any retain cycle.
As Tobi says in his answer, deinit gets called right before an object is deallocated.
An object gets deallocated when there are no longer any strong references to it. (Nobody owns the object any more.)
In order to answer your specific question you need to look at how your view controller is created an who owns it.
I haven't done much Mac development in a while, so I'm kinda rusty on view controller lifecycle, but here's my recollection of how it works:
If you only have one app window, it is the view controller's owner, and the window never gets closed, that means the view controller will never get deallocated.
If you quit the app I don't think the system tears down your window hierarchy before terminating the app (unless you're app is a document-based app, in which case the app will be told to close all of it's document windows before quitting.)
A deinitializer is called immediately before a class instance is
deallocated.
Altho your question is not clear to me.
But the usual reason for failure to trigger deinit when expected is that you have a retain cycle that prevents your view controller from going out of existence.
Sometimes the reason is that your expectation that the view controller would be destroyed under the circumstances is incorrect.
But assuming it is correct, a retain cycle is the reason.
another suggestion is to use Hierarchies Debug.
answer those questions for your self.
is this the root UIViewController ?
is it dismissed properly ?
Related
I am wondering if there is any way that can solve the ViewModel executing before the AppDelegate in my macOS app.
Suppose I have these files:
AppDelegate.swift
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
UserDefaults.shared.register(defaults: [ // <--- breakpoint 1
"userDefaultItem1" : false,
"userDefaultItem2" : 0
])
}
}
AppName.swift
#main
struct AppName: App {
#NSApplicationDelegateAdaptor(AppDelegate.self)
private var appDelegate // <--- breakpoint 2
#StateObject
var vm = ViewModel() // <--- breakpoint 3
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(vm)
}
}
}
When I run the application, it hits breakpoint 2, then breakpoint 3, then breakpoint 1.
As a result, none of the UserDefaults are registered prior to access in the ViewModel and it crashes.
Is this something that is supposed to happen, and though the use of AppDelegates are not required, there is use in legacy code that is now moved over to SwiftUI.
if you want to make sure that something is processed before even AppDelegate gets its delegate methods called then you could also place UserDefaults code in init of your AppDelegate. Or also possible, simply move the code over to applicationWillFinishLaunching which is called earlier.
init() { .. } will work because NSObjects always follow the alloc-init pattern. So init gets called first (second after alloc) before even any delegate method is called.
Also when your App crashes when UserDefaults are missing, then it is worth testing what happens when there are no UserDefaults used at all. This will force you to code a fallback and then you apply UserDefaults onto those prepared fallback properties "refreshing" them and go on from there. That makes sure when a User opens your app the first time, where most likely there are no UserDefaults set, it will not crash.
In short: using UserDefaults without fallback is a design pattern issue. You should always provide evaluation of UserDefaults before applying and sometime even before changing them, because Users can even erase them or otherwise manipulate them on a mac.
PS: your ViewModel is called very likely via an entry in Storyboard or main nib and is accordingly marked in Info.plist to do so.
another point to think about is, when this particular UserDefault is needed for the ViewModel then it is also worth to recall and evaluate them in init() of the ViewModel instead of AppDelegate
It does not make sense to say "deinit is executed before viewDidLoad". If not witnessed by myself, I never believe it. However, it 100% really happens here on my Project(Xcode8 and Swift2.3).
Is there any reason for this weird to happen?
Sure, initialize your view controller to a local variable, don't access its view or add it to the view hierarchy and leave the current scope. The view controller will deinit and viewDidLoad() will never have been called.
Keep in mind that viewDidLoad() is only called the first time the view controller's view property is explicitly accessed, or when loadView() or loadViewIfNeeded() are called on the view controller.
Had a similar experience and found I was mistakenly calling the same segue twice. The first view controller constructed got de-initialized immediately (pre viewDidLoad).
I have an NSWindowController that shows a table and uses another controller as the data source and delegate for an NSTableView. This second controller displays information from an object, which is passed in by the NSWindowController. That controller in turns has the object set as a property by the AppDelegate. It looks like this:
class SomeWindowController: NSWindowController {
var relevantThing: Thing!
var someTableController: SomeTableController!
#IBOutlet weak var someTable: NSTableView!
override func windowDidLoad() {
someTableController = SomeTableController(thing: relevantThing)
someTable.dataSource = someTableController
someTable.delegate = someTableController
}
}
In the AppDelegate I then do something like
func applicationDidFinishLaunching(_ aNotification: Notification) {
relevantThing = Thing()
someWindowController = SomeWindowController()
someWindowController.relevantThing = relevantThing
someWindowController.showWindow(nil)
}
Is this a reasonable approach? I feel like the implicitly unwrapped optionals used in SomeWindowController might be bad form. Also, relevantThing is not allowed to change in my case, so I feel a let would be more correct. Maybe the relevantThing should be made constant and passed in through the initializers? Or would that break the init?(coder: NSCoder) initializer?
I'd greatly appreciate any suggestions, as I'm trying to get a feel for the right way to do things in Swift.
A few things:
Is there any reason your are creating your window controller in code and not loading it from a storyboard/xib?
Generally, a better practice is to put all your 'controller' that relates to a view in a NSViewController and use NSWindowController only for stuff that relates to the window itself (e.g. toolbar, window management, etc).
Similarly to iOS, NSViewController is now integrated into the window/view lifecycle and responder chain. For many windows you don't even need to subclass NSWindowController.
XCode's app project template creates a storyboard with the window, main view and their controllers. This is a good starting point.
NSWindowController has a contentViewController property that is set to the NSViewController of the main content view (when loaded from storyboard). You generally don't need a separate view controller property for your view controller.
I think that usually, you want to minimize modifying your controllers from outside code and make them as independent as possible. This makes them more testable and reusable.
If your Thing instance is global for the entire application (as it appears from your code), you may want to consider adding it as a singleton instance to the Thing class and retrieving it from the NSViewController (e.g in viewDidLoad())
If you put your controllers/views in storyboard, you can connect the table's datasource/delegate there. And if this your main window, it can load and show it automatically when the app starts. But in any case, put your NSViewController/View wiring in the view controller.
If you want to separate logic between your main NSViewController into a more specialized view controller that handles a specific part of your view, you can use NSContainerView in Interface Builder to add additional view controllers to handle specific views.
Using Xcode 8, swift 3 and I create a iOS application using the game template with entities enabled. I notice I was seeing double node count for some initial sprites even though I only used addChild once.
I added
override func sceneDidLoad() {
print(#function) ... }
to the code and no idea why this is being called twice.
log file...
2016-09-20 10:21:31.482 MMDecon1[3295:791435] SKUtil.m: MGGetBoolAnswer is not available in the simulator.
sceneDidLoad()
sceneDidLoad()
I added
override func didMove(to view: SKView) {..}
and put my initialisation code in here as a temporary fix.
Does any one know why sceneDidLoad() is being fired twice with the default game app code using entities?
Normally, sceneDidLoad is only called one time. However, if a memory warning is sent then UIViewController releases its scene and sets it to nil if the view controller isn't visible. The next time that the scene appears the view controller will reload the scene and call sceneDidLoad again.
You have to assume that sceneDidLoad can be called multiple times.
Implement didReceiveMemoryWarning and log or set a breakpoint to see what is happening.
This seems like a newbie question but I've been struggling with it for a while now. I have some code initialisation that I'd like to run before my app launches.
At the moment I have set up my AppDelegate as follows:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate
{
func applicationDidFinishLaunching(aNotification: NSNotification)
{
runMyInitCode()
}
}
My problem is that all my object code runs before this. Where is the best place to call initialisation methods before anything else runs?
You have 4 options (in order of appearance):
Override init()
Override awakeFromNib()
Delegate notification applicationWillFinishLaunching
Delegate notification applicationDidFinishLaunching
The best place depends on your needs.
If IBOutlets are involved put it not before awakeFromNib.
If you are using a view based table view don't put it in awakeFromNib because it could be called multiple times.
Registering NSUserDefaults can be put everywhere.
Consider also lazy initialization.
Consider also Cocoa Bindings.