Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 2 if let with do-try-catch

Tags:

swift

In Swift 1.2 I have this:

if let filePath = NSBundle.mainBundle().pathForResource("some", ofType: "txt"),
       data = String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding) {
    for line in data.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) {
        // Do something
    }      
} else {
    println("some.txt is missing")
}

In Swift 2, I can no longer do this, because both pathForResource and contentsOfFile can throw, as well as returning optionals. I can fix it, but it now seems remarkably verbose:

do {
    if let filePath = try NSBundle.... {
        do {
            if let data = try String.... {
                for line in data..... {
                    // Do something
                }
            } else {
                print("Nil data")
            }
        } catch {
            print("contentsOfFile threw")
        }
    } else {
        print("Nil pathForResource")
    }
} catch {
    print("pathForResource threw")
}      

I expect I have missed something - any help appreciated.

like image 630
Grimxn Avatar asked Jun 09 '15 16:06

Grimxn


2 Answers

Use guard syntax instead of if-let.

Here is a sample:

do {

    guard let filePath = try NSBundle .... else {
        // if there is exception or there is no value
        throw SomeError
    }
    guard let data = try String .... else {
    }

} catch {

}

The difference between if-let and guard is scope of the unwrapped value. If you use if-let filePath value is available only inside the if-let block. If you use guard filePath value is available to scope that is guard called in.

Here is the relevant section in swift book

A guard statement, like an if statement, executes statements depending on the Boolean value of an expression. You use a guard statement to require that a condition must be true in order for the code after the guard statement to be executed. Unlike an if statement, a guard statement always has an else clause—the code inside the else` clause is executed if the condition is not true.

like image 75
mustafa Avatar answered Oct 22 '22 16:10

mustafa


Near as I can tell, only your String initializer in the above actually throws an error (although pathForResource() may change from returning an optional value to throwing an error at some point). Therefore, the below should replicate what you did before:

if let filePath = NSBundle.mainBundle().pathForResource("some", ofType: "txt") {
    do {
        let data = try String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding)
        for line in data.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) {
                // Do something
        }
    } catch {
        print("Couldn't load the file somehow")
    }
} else {
    print("some.txt is missing")
}

Your string is no longer optional, so there's no need for an if let there.

As mustafa points out, a guard statement could be used to remove a level of indentation in the success case:

guard let filePath = NSBundle.mainBundle().pathForResource("some", ofType: "txt") else {
    print("some.txt is missing")
}
do {
    let data = try String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding)
    for line in data.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) {
        // Do something
    }
} catch {
    print("Couldn't load the file somehow")
}

Now, if pathForResource() changed from returning an optional to throwing an error, you can simply use these try statements in sequence:

do {
    let filePath = try NSBundle.mainBundle().pathForResource("some", ofType: "txt")
    let data = try String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding)
    for line in data.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) {
        // Do something
    }
} catch {
    print("some.txt is missing")
}

The first statement to throw an error will kick out at that point, preventing execution of anything past that. A single catch statement is enough to pick up anything from a series of failable operations, which makes it easy to chain them.

like image 8
Brad Larson Avatar answered Oct 22 '22 17:10

Brad Larson