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
Related
I'm migrating my macOS app built with SwiftUI 1 to SwiftUI 2. There is a feature that allows user to save the main view as pdf using NSPrinterOperation, which takes an NSView instance.
Currently, the app is on SwiftUI 1 with AppDelegate, and I can get the main view through this code
Button("Print") {
let appDelegate = NSApp.delegate as! AppDelegate
let view = appDelegate.window.contentView! // returns the NSHostingView set up in AppDelegate.swift
...
NSPrintOperation(view: view, printInfo: newInfo).run()
}
Since upgrading to SwiftUI2, the AppDelegate is no longer there, and how can I get the view now?
See my comment for reference how to set app delegate (which you will be able then access), however also you can content view directly from window via NSApp, like
NSApp.mainWindow?.contentView
or
NSApp.keyWindow?.contentView
depending on your windows configuration.
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 ?
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.
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.
Were the delegate property ever to be nil, the app would be in an unrecoverable state.
class UIApplication
Declaration
unowned(unsafe) var delegate: UIApplicationDelegate?
Discussion
Every app must have an app delegate object to respond to app-related messages. For example, the app notifies its delegate when the app finishes launching and when its foreground or background execution status changes. Similarly, app-related messages coming from the system are often routed to the app delegate for handling. Xcode provides an initial app delegate for every app and you should not need to change this delegate later.
Why is UIApplicationDelegate.delegate defined as an optional? A nil value would be non-recoverable?
Actually, if I were to subclass UIApplication then I might be able to recover from delegate set to nil? Is this maybe why?
Here is a related question, but the AppDelegate is what holds the entire app together. I don't think it could be nil.
One reason might be to prevent AppDelegate from messing up with your unit tests.
Normally, when you are running unit tests, you actually don't ever have to interact with any UI components drawn onto screen so, initializing application's window property is totally unnecessary. Moreover, the code written in AppDelegate.swift (such as function calls in application(application:didFinishLaunchingWithOptions) might cause your unit tests to not run as intended so you may want the application delegate to never be instantiated.
For a typical iOS application, UIKit creates the application with the following function in main.swift:
UIApplicationMain(Process.argc, Process.unsafeArgv,
NSStringFromClass(UIApplication), NSStringFromClass(AppDelegate))
(As of Xcode 6, it is handled by the #UIApplicationMain decorator.)
UIApplicationMain initializes the application object and the application delegate whose type is the class name you passed to it, and sets up the event cycle. It's totally up to you to have it create the delegate object for you. If you don't need AppDelegate's UIWindow property, or any callback methods about handling push notifications, application's state etc., you create the application without it:
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), nil)
So that being said, if your unit tests don't depend on any of AppDelegate's methods to run, you can update main.swift as follows:
var delegateClassName: String? = NSStringFromClass(AppDelegate)
if NSClassFromString("XCTestCase") != nil {
// Unit tests are being run, so don't execute any code written in AppDelegate.swift
delegateClassName = nil
}
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), delegateClassName)
In order to know why/how this works you would have to know how UIApplication works internally, so any idea as to why or why not it's optional is purely conjecture.
There's likely a point where if the delegate is nil, the application either quits or crashes, or it's simply to allow initialization without setting the delegate and they chose not to force unwrap it so they can handle the error case.