When working with CoreData, we need access to the shared store which is available via the AppDelegate. To obtain a reference to the AppDelegate we can get it by doing the following:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate
else {
return
}
Why do we have to be so safe when downcasting to the Appdelegate? Can the downcast ever fail for some reason and if so, why?
I have looked around and can't find a reason as to why we write this code, and wanting to know why I write things will help!
Optional downcasting to AppDelegate (actually to the class representing the application delegate class) will never fail because the application won't even launch if the application delegate class was missing.
You can 100% safely write
let appDelegate = UIApplication.shared.delegate as! AppDelegate
Many people suffer from exclamationmarkophobia 😉 and argue that all exclamation marks are evil and you always have to guard any optional. This is wrong. The world is not only black and white. There are many cases where forced unwrapping an optional is safe like in this particular case.
Adding the #UIApplicationMain attribute to a class
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate { ... }
is equivalent to calling UIApplicationMain() in "main.swift"
with "AppDelegate" as the name of the class from which the application delegate is instantiated:
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv)
.bindMemory(
to: UnsafeMutablePointer<Int8>.self,
capacity: Int(CommandLine.argc)),
nil,
NSStringFromClass(AppDelegate.self)
)
(compare e.g. What does "#UIApplicationMain" mean? and Subclass UIApplication with Swift).
With either method, an instance of the AppDelegate class is created
and passed as delegate to the application object. So yes,
let appDelegate = UIApplication.shared.delegate as! AppDelegate
is safe.
But note that there is nothing special about the class name "AppDelegate", this is just convention. You can define the application
delegate as
#UIApplicationMain
class MyFantasticAppDelegate: UIResponder, UIApplicationDelegate { ... }
in which case it of course would be
let appDelegate = UIApplication.shared.delegate as! MyFantasticAppDelegate
So the precise statement would be
Casting UIApplication.shared.delegate to the type of the class defined as application delegate cannot fail.
By convention, that type is AppDelegate, but it doesn't have to be.
I can't agree with the point that force unwrapping downcasting to AppDelegate will never fail. It's isn't true. Because 'AppDelegate' could be the wrong type for your delegate. For example, I faced it when app used RxAppState framework. It has an extension for AppDelegate which returns 'RxAppState.RxApplicationDelegateProxy' class as the delegate. So always be careful.
Related
I did a small prototype which uses Firebase Cloud Messaging and the new SwiftUI 2 app life cycle. I added a custom AppDelegate via
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate and disabled method swizzeling for FCM to work. Everything is working as expected.
Today a colleague asked me, if one can get that delegate object via UIApplication.shared.delegate. So I gave it a shot and noticed that there seems to be two different AppDelegate objects:
po delegate prints:
<MyProject.AppDelegate: 0x6000009183c0>
where as po UIApplication.shared.delegate prints:
â–¿ Optional<UIApplicationDelegate>
â–¿ some : <SwiftUI.AppDelegate: 0x600000b58ca0>
Now I'm wondering what is the correct way of accessing the AppDelegate? Should one get it via an #EnvironmentalObject and pass it along all views? Or use the old fashioned way via UIApplication?
Additionally I would like to understand why I end up with two AppDelegates.
Your MyProject.AppDelegate is not direct UIApplicationDelegate, it is transferred via adapter to internal private SwiftUI.AppDelegate, which is real UIApplicationDelegate and which propagates some delegate callback to your instance.
So the solution might be:
Use #EnvironmentalObject if you need access to your MyProject.AppDelegate only in SwiftUI view hierarchy (for this AppDelegate must be confirmed to ObservableObject).
Add and use MyProject.AppDelegate static property which is initialized with object created via adapter, like
class AppDelegate: NSObject, UIApplicationDelegate {
static private(set) var instance: AppDelegate! = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
AppDelegate.instance = self // << here !!
return true
}
}
now everywhere in your code you can access your delegate via AppDelegate.instance.
I'm working through a tutorial and I noticed that when downcasting I didn't have to use an initializer method of the object. Is the object initialized? In the AppDelegate codebase below, I'm referring to the ItemsTableViewController. All I had to do was say "as!" but didn't need to use an init method or double parenthesis like this "ItemsTableViewController()".
Is the ItemsTableViewController initialized and if so how?
//
// AppDelegate.swift
// HomepwnerThirdTime
//
// Created by Laurence Wingo on 4/26/18.
// Copyright © 2018 Laurence Wingo. All rights reserved.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//create an ItemStore when the application launches
let itemStore = ItemStore()
//create an ItemsTableViewController
//set the window property which is of type UIWindow from the UIApplicationDelegate protocol and downcast this property to an initialized ItemsTableViewController by using as!
let itemsController = window!.rootViewController as! ItemsTableViewController
//now access the ItemsTableViewController and set its itemStore property since it is unwrapped in the ItemsTableViewController which needs to be set
itemsController.itemStore = itemStore
//we just set the itemStore property of the ItemsTableViewController! Yayy, WHEW!! Now when the ItemsTableViewController is accessed with that unwrapped ItemStore object then it will be set!!! WHHHHEEEEWWWWW!
return true
}
}
rootViewController sure is initialised. If it is not then it would have been nil, and casting it using as! would have caused an error. Here, by downcasting, you are not doing anything to the VC object. You are just telling Swift that "yes I'm sure this will be a ItemsTableViewController by the time the code is run, so don't worry about it".
How is the VC initialised then?
This has to do with how iOS handles the launching of an app. When you tap on an app, it opens and a UIWindow is created. Then the first VC in your storyboard is initialised and set as the rootViewController of the UIWindow. After doing all of that, your app delegate is called.
Note that when you are in the didFinishLaunching method, it's just that the VC has been created. The views in the VC are not loaded. That's what viewDidLoad is for.
Casting and initialization have nothing to do with each other.
A cast is simply a way to tell the compiler: "Trust me, even though you think this object is one type, I know it really is another type".
Initialization is of course the creation of a new object.
In your code, your view controller has already been created for you through your storyboard. Your code is simply accessing this already created view controller. The cast is you telling the compiler that the rootViewController is actually an instance of ItemsTableViewController and not a plain old UIViewController.
My app needs to perform multiple URL requests via Alamofire but I would like to perform these tasks independently on views or what user does in UI. This basically means "on background" so the handlers which actually performing the tasks would not be deinitialized until they are done.
My idea would be to call these requests from shared AppDelegate via some kind of class methods. I have only one question now:
What would be the best way to implement this scenario?
My actual knowledge would create something like this:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
class func someKindOfClassRequest() {
// ...
}
func someKindOfRequest() {
// ...
}
// ...
}
And I would call the method this way:
AppDelegate.someKindOfClassRequest()
or with not-class func, which of course will not solve the issue:
let sharedDelegate = UIApplication.shared.delegate as! AppDelegate
AppDelegate.someKindOfRequest()
As mentioned in the comments, Alamofire uses closure-based completions so they will not get deinitialized even if the calling object is deinitialized. For the sake of keeping your code well organized, why not just create a class that does these things instead of throwing it into your AppDelegate? For instance, create a class called BankgroundRequestController:
class BackgroundRequestController {
static let sharedInstance = BackgroundRequestController()
class func someKindOfClassRequest() {
// ...
}
}
Then you can call these functions like:
BackgroundRequestController.sharedInstance.someKindOfClassRequest()
I am getting swift_dynamiccast unconditional exception while accessing app delegate from test cases in one of the methods in application.
The function in the application is like this:
func sampleMethod()
{
var appdelegate:AppDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
}
Test case is accessing this method as:
func testStart()
{
var sample:MyClass = MyClass()
sample.sampleMethod()
}
It is raising exception in the method sampleMethod(), then it goes ahead. I have added MyClass & AppDelegate files in the test case project in build phases.
Any suggestions whats wrong here? A similar unanswered question here.
This is because AppDelegate object in the case of tests is different type than main project AppDelegate. Because of this your app is crash
class MyClass: NSObject {
func someMethod() {
var checkObject:AnyObject = UIApplication.sharedApplication().delegate!;
NSLog("%#", checkObject.description);
var appdelegate:AppDelegate = AppDelegate();
NSLog("%#", appdelegate);
}
}
You can see result of this function in console:
2015-01-14 13:03:58.299 TestSwift[654:282510] <TestSwift.AppDelegate: 0x17007b940>
2015-01-14 13:04:01.085 TestSwift[654:282510] <TestSwiftTests.AppDelegate: 0x17467f740>
Possible solution: use AnyObject variable instead of casting to AppDelegate
Did you add AppDelegate.swift to the test's target members?
Instead, try importing it from your application module.
And Rick's right. I faced a similar problem, solved it after going through
UIApplication.sharedApplication().delegate as AppDelegate causes EXC_BAD_ACCESS using it on swift unit test
I have seen numerous examples of UIWindow been declared as an Optional variable, like so,
var window: UIWindow?
My app only has one window and it will remain the same throughout its lifecycle. I'd argue it makes more sense to declare it as a constant.
And so I did. It doesn't raise any compiler errors (as of iOS 8.2) and seems to work just fine.
Why is nobody else doing this? Are there any pitfalls to doing this?
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
let window: UIWindow = UIWindow(frame: UIScreen.mainScreen().bounds)
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let viewController = ViewController()
window.rootViewController = viewController
window.makeKeyAndVisible()
return true
}
There are two parts to your question: let vs var, and optional vs non-optional.
For the first part, declaring an object property as let just means you expect the property to reference the same object throughout its lifecycle. Makes sense in your AppDelegate for a single-window application.
For the second part, UIWindow inherits its initializer from UIView; the documentation for UIView says that the initializer can return nil, although the Swift version isn't declared as being failable.
So if you're dealing with UIViews in other contexts, it probably makes sense to declare the variables as optional, or at least unwrapped, and be able to cope with the results.
That said, when your program is starting up, if the UIWindow fails to initialize, crashing immediately is probably a reasonable thing to do, as you won't really be able to do anything else. :)
I don't see any problem with how you've done it, but I'm open to hearing from others too.