Throw-Catch

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the Lesson01-Review-Basics playground, click Next to open the Lesson01-throw-catch page.

Bakery Model

Here’s a model for a bakery that sells pastries. The itemsForSale dictionary contains Pastry objects, each with flavor and numberOnHand properties.

class Pastry {
  let flavor: String
  var numberOnHand: Int
    
  init(flavor: String, numberOnHand: Int) {
    self.flavor = flavor
    self.numberOnHand = numberOnHand
  }
}

class Bakery {
  var itemsForSale = [
    "Cookie": Pastry(flavor: "ChocolateChip", numberOnHand: 20),
    "PopTart": Pastry(flavor: "WildBerry", numberOnHand: 13),
    "Donut" : Pastry(flavor: "Sprinkles", numberOnHand: 24),
    "HandPie": Pastry(flavor: "Cherry", numberOnHand: 6)
  ]
  
  func open(_ shouldOpen: Bool = Bool.random()) -> Bool {
    return shouldOpen
  }
    
  func orderPastry(item: String, amountRequested: Int, flavor: String) -> Int {
    let pastry = itemsForSale[item]
    pastry.numberOnHand -= amountRequested
    return pastry.numberOnHand
  }
}
let bakery = Bakery()
bakery.open()
bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: "ChocolateChip")

Error Protocol

Swift has an Error protocol, which forms the basis of its error-handling architecture. Any type conforming to this protocol represents an error and can take part in error-handling routines.

enum BakeryError: Error {
  case noInventory, noPower
  case tooFew(numberOnHand: Int), noSuchItem, wrongFlavor
}

Throwing an Error

Edit open(_:) to throw noInventory or noPower:

func open(_ shouldOpen: Bool = Bool.random()) throws -> Bool {  // 1
  guard shouldOpen else {  
    throw Bool.random() ? BakeryError.noInventory : BakeryError.noPower  // 2
  }
  return shouldOpen
}
func orderPastry(item: String, amountRequested: Int, flavor: String) throws -> Int {  // 1
  guard let pastry = itemsForSale[item] else {  // 2
    throw BakeryError.noSuchItem
  }
  guard flavor == pastry.flavor else {  // 3
    throw BakeryError.wrongFlavor
  }
  guard amountRequested <= pastry.numberOnHand else {  // 4
    throw BakeryError.tooFew(numberOnHand: pastry.numberOnHand)
  }
  pastry.numberOnHand -= amountRequested
      
  return pastry.numberOnHand
}

Catching an Error

If your code calls a function that can throw an error, you usually want to catch and handle any errors it throws. To do this, you try to call it, inside a do closure. Enclose the calls to bakery.open and bakery.orderPastry in a do closure and insert the keyword try:

do {
  try bakery.open()
  try bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: "Butter")
}
catch let error as BakeryError {
  switch error {
  case .noInventory, .noPower:
    print("Sorry, the bakery is now closed.")
  case .noSuchItem:
    print("Sorry, but we don't sell this item.")
  case .wrongFlavor:
    print("Sorry, but we don't carry this flavor.")
  case .tooFew(numberOnHand: let items):
    print("We only have \(items) of that item.")
  }
}
catch {
  print("Something went wrong: \(error)")
}
Wrong flavor
Qpizk qbeham

do {
  try bakery.open()
  try bakery.orderPastry(item: "Albatross", amountRequested: 1, flavor: "AlbatrossFlavor")
}
// Another way to handle every BakeryError
catch BakeryError.noInventory, BakeryError.noPower {
  print("Sorry, the bakery is now closed.")
} catch BakeryError.noSuchItem {
  print("Sorry, but we don't sell this item.")
} catch BakeryError.wrongFlavor {
  print("Sorry, but we don't carry this flavor.")
} catch BakeryError.tooFew(numberOnHand: let items) {
  print("Sorry, we only have \(items) of that item.")
} catch {
  print("Some other error.")
}
No such item
Du nupv ukah

Not Caring About an Error

If you don’t care about the error details, you don’t need do and catch closures. Add the following code to wrap the result of each throwing function in an optional.

let open = try? bakery.open(false)
let remaining = try? bakery.orderPastry(item: "Albatross", amountRequested: 1, flavor: "AlbatrossFlavor")
try? returns nil.
ftj? yasohjw cug.

Knowing Errors Won’t Happen

If you know your code is never going to fail, or you want your program to terminate if the function throws an error, use try! — add the following code and run it:

try! bakery.open(true)
try! bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: "Butter")
try! throws error and crashes.
wmr! ypxajb ipgod opk yyopzah.

do {
  try bakery.open(true)
  try bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: "Butter")
}
catch {
  fatalError()
}

The Problem With throws

You want the compiler to help you write the best possible code. Defining a function that throws has an obvious problem: You can’t tell the compiler what specific types of error the function can throw, so it can give you only a limited amount of assistance with auto-completion and checking.

See forum comments
Download course materials from Github
Previous: Optionals Next: Conclusion