Declaring UIWindow as let instead of optional var compiles fine. Any pitfalls? - ios

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.

Related

SwiftUI 2 accessing AppDelegate

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.

How come when downcasting I don't have to use an initializer of ItemsTableViewController? Is it initialized?

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.

Can 'downcasting' to AppDelegate ever fail?

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.

Routing Viper architecture

How to create good routing in project on Viper architecture? I start create file for routing but i don't understand what i must do next.
I create a file wireframe first controller and wireframe protocol:
// ChooseLanguageWireframeProtocol.swift
import UIKit
#objc protocol ChooseLanguageWireframeProtocol {
func presentChooseLanguageViewControllerWindow()
func presentAuthScreenViewController()
}
in file wireframe i add:
// ChooseLanguageWireframe.swift
import UIKit
class ChooseLanguageWireframe: NSObject , ChooseLanguageWireframeProtocol{
var chooseLanguageScreenViewController: ChooseLanguageViewController?
var window: UIWindow?
func presentChooseLanguageViewControllerWindow() {
let chooseLanguageViewController = UIStoryboard.init(name: "ChooseLanguage", bundle: nil).instantiateViewController(withIdentifier: "ChooseLanguage") as? ChooseLanguageViewController
self.chooseLanguageScreenViewController = chooseLanguageViewController
self.window!.rootViewController = chooseLanguageScreenViewController
self.window!.makeKeyAndVisible()
}
func presentAuthScreenViewController() {
}
}
After i create RootWireframe
// RootWireframe.swift
import UIKit
class RootWireframe: NSObject {
let chooseLanguageScreenWireframe : ChooseLanguageWireframe?
override init() {
//What i must init??
}
func application(didFinishLaunchingWithOptions launchOptions: [AnyHashable: Any]?, window: UIWindow) -> Bool {
self.chooseLanguageScreenWireframe?.window = window
return true
}
}
In file AppDelegate i change only
var window: UIWindow?
let rootWireframe = RootWireframe()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.rootWireframe.application(didFinishLaunchingWithOptions: launchOptions as [NSObject : AnyObject]?, window: self.window!)
}
What i must add or change for correctly work?
One way to do it is to inherit from UINavigationController and do the routing logic in there. You may end up with multiple classes doing routing in different parts of your application depending on your task.
But like with any cromulent buzzword you firstly have to ask yourself
if the "solutuon" solves more issues than it creates.
It starts to make sense when you get two dozen screens and start to get lost in
the very same issue that got resolved ages ago by adding storyboards to
plain old xibs.
So you really have to step back and ask yourself if you really want to follow
this cromulent architecture creating lots and lots of classes in the process,
a maze that will be hardly more readable than a standard MVC project.
If you can not use storyboards and want to use VIPER do so,
if you can use storyboards do VIPE :-)
But in most projects the datamodel is so pathetically simple and the
presentation is so tightly coupled that you have zero need in
UIViewController defattening.
And I suspect that the cure applied by the most viper affictionados is
worse than the decease itself.
2 possibilities:
Change the init of your rootViewframe to accept your dependency and initialize it.
And btw your variable type should not be the real type but the protocol, so you can mock it easily in tests
let chooseLanguageScreenWireframe : ChooseLanguageWireframeProtocol?
override init(chooseLanguage: ChooseLanguageWireframeProtocol) {
self.chooseLanguageScreenWireframe = chooseLanguage
}
And then create your chooseLanguageWireframe implementation in the appdelegate and pass it in the constructor.
This makes the dependency clear and visible. You can also remove the optionality, since you always initialize it.
Or solution 2
Create the chooseLanguageWireframe in the appdelegate and inject it there outside constructor
lazy var rootWireframe = {
let r = RootWireframe()
r.chooseLanguageScreenWireframe = self.chooseLanguageScreenWireframe
return r
}
In any case, you need to instantiate your dependencies somewhere, they cannot get created automatically.
You usually do that with factories and with the help of a dependency injection framework (check SwiftInject or Typhoon)
Also, declare all your dependencies with protocols type, it is the purpose of the VIPER architecture to simplify testing and mocking with isolations between actors

performSelector error with global function and AppDelegate class

I'm following this apple document and I'm trying to translate some of its parts in Swift language. I have this global function, with performSelector:
func RunLoopSourceScheduleRoutine(info:UnsafeMutableRawPointer? ,rl:CFRunLoop? , mode:CFRunLoopMode?) {
let obj : RunLoopSource = Unmanaged<RunLoopSource>.fromOpaque(info!).takeUnretainedValue()
let del = UIApplication.shared
let theContext = RunLoopContext(withSource: obj, andLoop: rl!)
del.performSelector(onMainThread:#selector(AppDelegate.registerSource) , with: theContext, waitUntilDone: false)
}
And AppDelegate class, in this class there are: methods that automatically adds Xcode in the normal routine of project creation (didFinishLaunchingWithOptions, applicationWillResignActive, etc) I added the sourcesToPing parameter and the registerSource() method:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var sourcesToPing : [RunLoopContext] = Array()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func registerSource(sourceInfo:RunLoopContext) {
sourcesToPing.append(sourceInfo)
}
}
but the compiler get the following error , in RunLoopSourceScheduleRoutine() function:
argument '#selector' refers to instance method 'registerSources(source Info:)' that is not exposed to Objective-C
what is the problem ? and how does it solve?
PerformSelector is an Objective-C method that predates GCD (Grand Central Dispatch). It should be possible to do it that way, but selectors are not type-safe and are awkward to use.
I'm not sure what's wrong with your current code. As Martin points out in his comment, the error you're reporting is complaining about a method called registerSources() but you show code for a method called registerSource() (with no final "e".) If you want to get that code working you need to get to the bottom of that discrepency.
Instead, why not use GCD code like this:
dispatchQueue.main.async() {
registerSource(theContext)
}
That will accomplish the same goal but using the more modern GCD

Resources