Swift 2: Day One (do-try-catch)


It's been a fairly successful day so far working with the latest release of Swift. Migration of Aldwych appears to have gone smoothly using Edit -> Convert -> To Latest Swift Syntax... and fixing the stray errors only took 10 mins or so. (The next step is to check that the functionality all remains intact, but I'll do this over the coming week or so.)

The second repository I converted was SwiftFiles and this one got a bit tied in knots over try and catch statements. The reason for this is that certain Cocoa methods that were happy before to supply error information to a pointer now insist that you handle errors with try and catch.

A good example of this is NSFileManager. No longer can this be written:
NSFileManager.defaultManager().contentsOfDirectoryAtURL(url, includingPropertiesForKeys: properties, options:NSDirectoryEnumerationOptions.SkipsHiddenFiles, error: &error)
Instead the following must be used:
do {
    try NSFileManager.defaultManager().contentsOfDirectoryAtURL(url, includingPropertiesForKeys: nil, options: [])
}
catch let error as NSError {
    error.description 
}
And where there are if-let chains, as in the following code,
let url = NSURL(fileURLWithPath: loadPath)
var error:NSError?

var properties = [NSURLLocalizedNameKey,
            NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey]

if let url = url,
       array = NSFileManager.defaultManager().contentsOfDirectoryAtURL(url, includingPropertiesForKeys: properties, options:NSDirectoryEnumerationOptions.SkipsHiddenFiles, error: &error) as? [NSURL] {
        return array
        }
the compiler sometimes became confused. Things are doubly complex in this example because the fileURLWithPath: instantiation of an NSURL in Swift 2.0 does not return an optional, as it did in Swift 1.2, so if-let is not required for either the url or the array (returned by the NSFileManager method). But with a little manual help the code (and the method within which it is contained) can be rewritten:
public static func allFilesAndFolders(inDirectory directory:NSSearchPathDirectory, subdirectory:String?) -> [NSURL]? {

    // Create load path
    if let loadPath = buildPathToDirectory(directory, subdirectory: subdirectory) {
        
    let url = NSURL(fileURLWithPath: loadPath)
    var array:[NSURL]? = nil
            
    let properties = [NSURLLocalizedNameKey,
        NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey]

    do {
         array = try NSFileManager.defaultManager().contentsOfDirectoryAtURL(url, includingPropertiesForKeys: properties, options:NSDirectoryEnumerationOptions.SkipsHiddenFiles)          
        }
    catch let error1 as NSError {
          print(error1.description)
    }
    return array
    }
  return nil
}
The important thing to note, however, is that the do-try-catch syntax won't be required for all NSFileManager methods, because contentsAtPath(), for example, returns nil rather than an error if no data can be returned. Similarly, NSData's contentsOfFile() method is not a throwing function, but NSString's contentsOfFile() is, and so will require do-try-catch. Indeed, all functions marked with the throws keyword require that whenever they are employed that the try and catch syntax is used. And with this knowledge in place I could very quickly update the Swiftography repository as well. So that was three repositories for which I could create Swift 2 branches in the space of an hour with the only difficulty to overcome being to understand the new do-try-catch syntax.

Beyond Cocoa integration

The error handling in Swift isn't confined to handling interaction with Cocoa frameworks. It is there for you to explore in your own code too. And this is done by first creating an Error type that adopts the ErrorType protocol:
enum Error:ErrorType {
    case WrongTurn
    case LowBridge
}
Next a throwing function is composed:
func continueForwards(direction:String) throws -> Bool  {

    if direction == "north" {
        return true
    }
    throw Error.WrongTurn
    
}
(Notice that the throw excuses the need to return anything else aside from the error, thus providing an alternative to the returning of optionals when things do not succeed.) Finally, we're ready to try and catch, not forgetting to begin with the do word.
do {
    try continueForwards("south")
}
catch Error.WrongTurn {
    "Wrong turn!" // "Wrong turn!"
}
catch {
}
Not too tricky but using do I found a bit jarring and counterintuitive at first. Another thing to note is that final catch: in Playgrounds you won't need it but inside an app the compiler will complain that you've not been exhaustive if it's not there. It's almost equivalent to the default in a switch statement. But it doesn't need to standalone, it can instead capture a value, like so:
catch let error {
// do something with error
}
The important thing is that the compiler doesn't know all the Error types you might have or which might be thrown, so even if you list them all, it'll still complain. Therefore, you need this generic handling of errors as well to cover all eventualities, so one of the best ways to handle a situation where you know the type of errors being thrown is probably to employ switch statements:
do {
        try continueForwards("south")
    }
    catch let error {
        if let e = error as? Error {
        switch e {
        case .WrongTurn:
            print("Wrong Turn!")
        case .LowBridge:
            print("Low Bridge!")
            }
        }
    }
This cuts down on multiple catch statements and the need to end with a generic catch all, because you are catching all the first time and then examining what you've caught.

What else?

The other related item to be aware of is that now do is being employed in this try and catch scenario, repeat has taken its old job in the do-while loop, which now becomes repeat-while. For now that's all I have to report, but there's plenty more to be explore with Swift's new ErrorType, which you can read about in the latest release of Apple's Swift eBook.

I'd add to this a small piece of advice, if you are making code adjustments in response to compiler errors then sometimes they'll remain once the error is fixed. To cure this close and reopen the project file and often the error messages will then clear. If not you still have errors.

One further note: in Swift 2.0 compiler warnings now include situations where variables can be constants, which in a few instances worked incorrectly for me, but what I do really like is that the compiler lets me know when I haven't utilised a constant or variable from an if-let statement (and elsewhere) and can replace it with an underscore, thus reducing the number of values that are unnecessarily stored, which will no doubt lead to memory savings.


Endorse on Coderwall

Comments