Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 881
Bryon Avatar asked Dec 29 '15 13:12

Bryon


People also ask

Do try catch Swift error?

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-catch vs try catch Swift?

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.

What is error in Swift?

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!).

How do I capture a fatal error in Swift?

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.


2 Answers

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.

like image 198
Gordon Dove Avatar answered Oct 22 '22 08:10

Gordon Dove


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.

like image 23
Prajeet Shrestha Avatar answered Oct 22 '22 08:10

Prajeet Shrestha