I have a question about exception handling in Swift. The UIKit documentation for the UIStoryboard class states that the instantiateViewControllerWithIdentifier( identifier: String ) -> UIViewController function will throw an exception if the identifier is nil or does not exist in the storyboard. However, if I use a do/try/catch like the following, I receive a warning "No calls to throwing functions occur within 'try' expression."
It is only a warning so I figured that it was a intellisense issue; but when I run the following code and deliberately use an invalid identifier no exception is caught and a SIGABRT is generated.
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
do {
let controller = try storyboard.instantiateViewControllerWithIdentifier("SearchPopup")
// This code is only included for completeness...
controller.modalPresentationStyle = .Popover
if let secPopoverPresentationController = controller.popoverPresentationController {
secPopoverPresentationController.sourceView = self.view
secPopoverPresentationController.permittedArrowDirections = .Any
secPopoverPresentationController.barButtonItem = self.bSearchButton
}
self.presentViewController(controller, animated: true, completion: nil)
// End code included for completeness.
}
catch {
NSLog( "Exception thrown instantiating view controller." );
return;
}
How are you supposed to do/try/catch for functions that throw exceptions like this?
Thanks in advance.
Bryan
The try/catch syntax was added in Swift 2.0 to make exception handling clearer and safer. It's made up of three parts: do starts a block of code that might fail, catch is where execution gets transferred if any errors occur, and any function calls that might fail need to be called using try .
do – This keyword starts the block of code that contains the method that can potentially throw an error. try – You must use this keyword in front of the method that throws. Think of it like this: “You're trying to execute the method.
There are four ways to handle errors in Swift: — You can propagate the error from a function to the code that calls that function. — Handle the error using a do - catch statement. — Handle the error as an optional value (try?). — Assert that the error will not occur (try!).
You are not supposed to catch fatalerror. It indicates a programming error. You don't catch programming errors, you fix your code. The crash is intentional and it is intentional that you cannot stop it.
This is a specific case of the more general issue discussed in Catching NSException in Swift
The summary seems to be that swift exceptions and objc exceptions are different.
In this instance, the swift documentation says it throws an Exception, but this cannot be caught; which sounds like a documentation bug at the very least.
I don't agree with the other answers here that a missing VC is clearly a programmer error. If the behaviour was as documented, one can postulate a design where common code reacts differently depending on whether a VC is present or not in a particular case|product|localisation. Having to add additional config to ensure that loading the VC is only attempted when it is present is an invitation to edge case bugs and the like. c.f. update anomalies.
let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
This method doesn't throw error and doesn't return nil if storyboard is not found so it will just crash the app in runtime. That's inevitable. But I have figured out the way to make sure runtime crashes due to this exception won't happen. And it's through Unit-test.
There are some conventions though:
enum AppStoryboard:String, CaseIterable {
case Main
case Magazine
case AboutUs
var storyboard:UIStoryboard {
return UIStoryboard(name: self.rawValue, bundle: nil)
}
}
After this make a unit test class for checking if all the storyboard that we need exists.
class AppstoryboardTests: XCTestCase {
func testIfAllStoryboardExistInBundle() throws {
let storyboards = AppStoryboard.allCases
for sb in storyboards {
_ = sb.storyboard
}
XCTAssert(true)
}
}
If all the storyboard defined in Appstoryboard doesn't exist in bundle, the test will just fail with an exception. If all storyboard is available the test will pass.
This method is little unorthodox but, better than app crashing in runtime. :)
Note: If you are not comfortable with Unit Test Put this in AppDelegate in didFinishLaunchingWithOptions method like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let storyboards = AppStoryboard.allCases
for sb in storyboards {
_ = sb.storyboard
}
return true
}
The first thing app will do if storyboard is not found is crash.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With