crazy_boy7 crazy_boy7 - 6 months ago 19
iOS Question

Catching a default error in Swift

I am trying to learn Swift, and wondered about Apple's expected answer to the experiment below which is provided in the Error Handling section of Apple's GuidedTour playground as follows;


Experiment: Add code to throw an error inside the do block. What kind of error do you need to throw so that the error is handled by the first catch block? What about the second and third blocks?


enum PrinterError: ErrorType {
case OutOfPaper
case NoToner
case OnFire
}

func sendToPrinter(printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.NoToner
}
return "Job sent"
}

do {
let printerResponse = try sendToPrinter("Gutenberg")
print(printerResponse)
} catch PrinterError.OnFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}





Below are my answers to the questions asked in this experiment, and they all seem to work...


What kind of error do you need to throw so that the error is handled by the first catch block?


By adding throw PrinterError.OnFire as below;

do {
throw PrinterError.OnFire
let printerResponse = try sendToPrinter("Gutenberg")
print(printerResponse)
} catch PrinterError.OnFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}



What about the second...


By sending "Never Has Toner" to sendToPrinter.

do {
let printerResponse = try sendToPrinter("Never Has Toner")
print(printerResponse)
} catch PrinterError.OnFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}



...and third blocks?


The only way I can trigger the third block -and the expected answer in my opinion in this case- is to create a different enum conforming to ErrorType and to throw it, as below;

enum anotherEnum: ErrorType {
case someCase
}

do {
throw anotherEnum.someCase
let printerResponse = try sendToPrinter("Gutenberg")
print(printerResponse)
} catch PrinterError.OnFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}


However, as the first sentence for this experiment starts with "Add code to throw an error inside the do block.", I wondered if there was another expected answer that I was supposed to find out while staying in the do block.

I'm asking because I don't want to miss something else that I was expected to find out. Do you think these answers are valid from Apple's perspective?

Thank you!

Rob Rob
Answer

A more practical example of that third scenario might be something like checking to see if the network was available before even bothering to try sending something to the network printer:

enum NetworkError: ErrorType {
    case Unavailable
}

enum PrinterError: ErrorType {
    case OutOfPaper
    case NoToner
    case OnFire
}

func sendToPrinter(printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.NoToner
    }
    return "Job sent"
}

var networkAvailable = false     // some other process will update this when the network is available

Then you could do:

do {
    if !networkAvailable {
        throw NetworkError.Unavailable
    }

    let printerResponse = try sendToPrinter("Gutenberg")
    print(printerResponse)
} catch PrinterError.OnFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}

This will result in the third catch block being caught.

Or a more realistic, Cocoa example might be:

do {
    let documents = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
    let fileURL = documents.URLByAppendingPathComponent("document.txt")
    let contents = try String(contentsOfURL: fileURL)

    let printerResponse = try sendToPrinter(contents)
    print(printerResponse)
} catch PrinterError.OnFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}

Note, this isn't actually manually throwing an error, but relying upon errors thrown by URLForDirectory and String(contentsOfURL:). But if the document.txt was not found in the Documents folder, String(contentsOfURL:) would throw an error which would be caught by your third catch block.