So do the following. We're only concerned about the init statement here, body is standard and unimportant.
@main
struct SomeApp: App {
init() {
print(UIApplication.shared) // Crash = EXC_BAD_ACCESS (code=1, address=0x0)
}
var body: some Scene {
...
}
}
App crashes at the print statement. Okay. Fine.
Now try this...
@main
struct SomeApp: App {
init() {
print(UIApplication.shared.isNetworkActivityIndicatorVisible) // prints false!!!
}
var body: some Scene {
...
}
}
The app DOESN'T crash and the print statement prints false.
Breakpoint the print statement and check in the debugger....
(lldb) po UIApplication.shared
<uninitialized>
(lldb) po UIApplication.shared.isNetworkActivityIndicatorVisible
false
Can someone explain the black magic that seems to be going on here??? Does dynamic dispatch have some sort of secret understanding with UIApplication???
UIApplication.shared is declared to return UIApplication, so if you're accessing that value in a context where it's actually giving you a nil reference from the UIKit side, that's going to cause problems for Swift (because on the Swift side of things, we won't be performing any nil checks).
But UIApplication is also an Objective-C class, and so an access like UIApplication.shared.isNetworkActivityIndicatorVisible is going to become an Objective-C message send to UIApplication.shared. It's perfectly valid in Objective-C to send messages to nil, and you'll get back 'reasonable' values for many return types (such as false for Bool values). More info here.
Relevant quote: " Note: If you expect a return value from a message sent to nil , the return value will be nil for object return types, 0 for numeric types, and NO for BOOL types. Returned structures have all members initialized to zero."
I knew this in another lifetime, but I've lived in Swift for so long that my Objective-C-fu is weak.
Working in a mixed Objective-C/Swift codebase for long enough will give you a strong spidey-sense that whenever you see EXC_BAD_ACCESS (code=1, address=0x0000000000000xx) (i.e., any small address) it's probably because you've returned a nil object from Objective-C land to a Swift API that was marked as non-optional.
I think this would be worth feedback to Apple to add documentation that it's not valid to call UIApplication.shared prior to UIApplicationMain (which SwiftUI calls on your behalf afterApp.init is called).
And maybe separately, documentation that App.init is called before UIApplicationMain or NSApplicationMain.
It has, at least, been true for most, if not all, of UIKit's lifetime. However prior to the SwiftUI app lifecycle support, it was not as easy to accidentally call into UIApplication.shared prior to UIApplicationMain.