Bryon
Bryon

Reputation: 1017

exception handling with swift

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

Answers (4)

Prajeet Shrestha
Prajeet Shrestha

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)
    }
}
  1. You should only initialize storyboard through this enum. This enum is CaseIterable so you can access all cases with AppStoryboard.allCases

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

Gordon Dove
Gordon Dove

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

Midhun MP
Midhun MP

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

luk2302
luk2302

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

Related Questions