Reputation: 1017
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
Upvotes: 8
Views: 3823
Reputation: 8108
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.
Upvotes: 1
Reputation: 2577
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.
Upvotes: 8
Reputation: 107231
The instantiateViewControllerWithIdentifier is not a throwing function and you can't handle it using do...try...catch. If the view controller is not available in the storyboard, there is nothing you can do. It's a programmer mistake, the one who created that, should handle such issues. You can't blame iOS runtime for such kind of errors.
Upvotes: 0
Reputation: 57204
You do not recover from that exception, that exception is a RuntimeException
.
Simply ask yourself: "how would I react to it?" - if the answer is "I do not know" then why would you even want to catch it?
Take the example of the incorrect identifier - what would you do when catching the error???
You cannot recover in any way that makes sense. If the identifier you pass in cannot be found that is something you as the developer did wrong when creating the app. That is something that would and should be obvious when testing the app. If you somehow miss it your app will crash in Review by Apple or on the client's device.
Upvotes: 0