Search code examples
swifterror-handlingnsdate

How to handle errors/exceptions in swift


That title sounds pretty general although its a specific problem. I just didn't know how to describe it in a better way... I've read quite a lot about error handling in swift. But I still cant figure out how to solve this simple (at least in objective-C) task. I have a textfield and want to read out the string-value...than format it in an Date value. No problem so far. But if you type in a value that does not match the specified date format I get an error back (which is ok...its not the right format). In this case I just want a standard value to be saved. In Objective-C I would have done this with a @try-@catch loop. That kind of exception handling does not exist in swift anymore does it? Here is my (Swift 2) code. Hope somebody has an answer.

func getDateFromTextfield() throws -> NSDate
{
    let formatter:NSDateFormatter = NSDateFormatter()
    formatter.dateFormat = "dd.MM.yyyy"
    formatter.timeZone = NSTimeZone(name: "UTC")

// Here should be a "throw expression" but I dont know how to set it. 

    let date = formatter.dateFromString(dictOfLabelsAndText["Birthdate"] as! String)!

    return date
}

do
{
    child.birthdateAT = try self.getDateFromTextfield()
}
catch {
    child.birthdateAT = formatter.dateFromString("01.01.2000")
    print("wrong date") //Or whatever error
}

Solution

  • 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.