Swift Error Handling for Methods That Do Not Throw

Catching an error from a non-throwing method

You can use the init(exactly:) constructor, it will not throw an error but it will return nil if the value is to large

guard let value = Int(exactly: pow(Double(1000000000), Double(10))) else {
//error handling
}

Best practice for Swift methods that can return or error

To add an answer to this question (five years later), there’s a dedicated Result type for this exact scenario. It can return the type you want on success, or type an error on failure.

It does mean re-factoring some code to instead accept a completion handler, and then enumerating over the result in that callback:

class SecurityService {
static func loginWith(email: String, password: String, completionHandler: @escaping (Result<User, SecurityError>) -> Void) {
// Body
}
}

Then in a handler:

securityService.loginWith(email: email, password: password) { result in
switch result {
case .success(let user):
// Do something with user
print("Authenticated as \(user.name)")
case .failure(let error):
// Do something with error
print(error.localizedDescription)
}
}

An elegant way to ignore any errors thrown by a method

If you don't care about success or not then you can call

let fm = NSFileManager.defaultManager()
_ = try? fm.removeItemAtURL(fileURL)

From "Error Handling" in the Swift documentation:

You use try? to handle an error by converting it to an optional value.
If an error is thrown while evaluating the try? expression, the value
of the expression is nil.

The removeItemAtURL() returns "nothing" (aka Void), therefore the return value of the try? expression is Optional<Void>.
Assigning this return value to _ avoids a "result of 'try?' is unused" warning.

If you are only interested in the outcome of the call but not in
the particular error which was thrown then you can test the
return value of try? against nil:

if (try? fm.removeItemAtURL(fileURL)) == nil {
print("failed")
}

Update: As of Swift 3 (Xcode 8), you don't need the dummy assignment, at least not in this particular case:

let fileURL = URL(fileURLWithPath: "/path/to/file")
let fm = FileManager.default
try? fm.removeItem(at: fileURL)

compiles without warnings.

How to handle errors/exceptions in swift

The Swift do-try-catch syntax is designed to handle the errors analogous to the NSError patterns in Objective-C, not for handling fatal errors/exceptions. Do not be confused by the similarity of Swift's error handling do-try-catch with the completely different Objective-C exception handling pattern of @try-@catch.

One should only use forced unwrapping (!) and forced type casting (as!) when one knows with certainty that they cannot possibly fail. If they could fail (as in this case), you should gracefully detect this scenario and handle it accordingly.

You could, for example, use Swift error handling to communicate a failure converting a string to a date (and then use do-try-catch pattern when you call it in order to detect and handle that error):

enum DateError: Error {
case badDate
case dateNotFound
}

func getDateFromTextfield() throws -> Date {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)

guard let dateString = dictOfLabelsAndText["Birthdate"] as? String else {
throw DateError.dateNotFound
}

guard let date = formatter.date(from: dateString) else {
throw DateError.badDate
}

return date
}

Then you could do:

do {
child.birthdateAT = try getDateFromTextfield()
} catch {
child.birthdateAT = formatter.date(from: "01.01.2000")
}

Or, more concisely, use try? and nil coalescing operator:

child.birthdateAT = try? getDateFromTextfield() ?? formatter.date(from: "01.01.2000")

Alternatively, you might just change the method to return an optional and use nil as a way of detecting a failure:


func getDateFromTextfield() -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)

guard let dateString = dictOfLabelsAndText["Birthdate"] as? String else {
return nil
}

return formatter.date(from: dateString)
}

And then do:

if let birthDate = getDateFromTextfield() {
child.birthdateAT = birthDate
} else {
child.birthdateAT = formatter.date(from: "01.01.2000")
}

Or, again, use the nil coalescing operator:

child.birthdateAT = getDateFromTextfield() ?? formatter.date(from: "01.01.2000")

But, bottom line, do not use ! to unwrap an optional unless you know it can never be nil. Otherwise, use optional binding and/or guard statements to gracefully detect and report the failure.


This has been updated for contemporary versions of Swift. For original Swift 2 answer, see previous revision of this answer.

Swift 2.1 do-try-catch not catching error

The do - catch combination is fine. This issue is simply one that cannot be caught - and therefore never makes it to the catch block.

If the issue were catchable (defined and handled via Swift's throws functionality), the catch block would've been executed.


Some semantics: there is a long-standing argument about the differences between the terms error and exception.

Some developers consider the two terms to be distinct. In this case, the term error represents an issue that was designed to be handled. Swift's throws action would fit here. In this case, a do - catch combination would allow the issue to be caught.

An exception, for these developers, represents an unexpected, usually fatal, issue that cannot be caught and handled. (Generally, even if you could catch it, you would not be able to handle it.)

Others consider the two terms to be equivalent and interchangeable, regardless of whether the issue in question can be caught or not. (Apple's documentation seems to follow this philosophy.)

(Updated to focus on the answer rather than the semantics.)

Error handling on native methods swift

This method doesn't throw, so you can't catch the error. It seems the only way to be sure the function call will not fail is to manually check that the range is valid.

You could for example get the range from the string itself with something like rangeOfString(:)

(attributedString.string as NSString).rangeOfString("foo")

Or simply validate your NSRange bounds against the string.

if (range.location != NSNotFound && range.location + range.length <= attributedString.length) { /* ok */ }


Related Topics



Leave a reply



Submit