Error handling Beacon scanning I've recently began trying to put more error handling in to my code so I thought I'd post a use-case to check I'm on the right path. I feel like this is going to make my functions much larger and more indented/nested than before. Is this the right way to go about things or should I be utilising try? instead? Where do people generally put these Error enum definitions?

fileprivate enum LocationError: Error {
    case noAuthorization
    case noBeaconSupport
    case rangingUnavailable
}

class ViewController: UIViewController, CLLocationManagerDelegate {
    @IBOutlet weak var distanceReading: UILabel!
    var locationManager: CLLocationManager!

override func viewDidLoad() {
    super.viewDidLoad()

    locationManager = CLLocationManager()
    locationManager.delegate = self
    locationManager.requestWhenInUseAuthorization()

    view.backgroundColor = UIColor.gray
}

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    do {
        guard status == .authorizedWhenInUse else {
            throw LocationError.noAuthorization
        }
        guard CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) else {
            throw LocationError.noBeaconSupport
        }
        guard CLLocationManager.isRangingAvailable() else {
            throw LocationError.rangingUnavailable
        }
        startScanning()
    }
    catch LocationError.noAuthorization {
        print("User has not authorized us to use location")
    }
    catch LocationError.noBeaconSupport {
        print("User's device does not support Beacons")
    }
    catch LocationError.rangingUnavailable {
        print("User's device does not support ranging")
    }
    catch {
        fatalError()
    }
}
Throwing Swift errors is a mechanism how a function/method can report a failure to its caller. Your code throws and catches the error within the
same method, and I can see no advantage of using try/catch in that
situation. Your code is equivalent to

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    guard status == .authorizedWhenInUse else {
        print("User has not authorized us to use location")
        return
    }
    guard CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) else {
        print("User has not authorized us to use location")
        return
    }
    guard CLLocationManager.isRangingAvailable() else {
        print("User's device does not support ranging")
        return
    }
    startScanning()
}


which is


shorter and easy to understand,
makes the enum LocationError obsolete (which is fileprivate,
and therefore apparently not used anywhere else),
makes the catch-all with fatalError() obsolete.


I would even go a step further: guard is useful in connection with
optional binding (to avoid the "optional binding pyramid of doom").
In your case, the same can be achieved with a simple if/else if/.../else statement:

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    if status != .authorizedWhenInUse {
        print("User has not authorized us to use location")
    } else if !CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
        print("User has not authorized us to use location")
    } else if !CLLocationManager.isRangingAvailable() {
        print("User's device does not support ranging")
    } else {
        startScanning()
    }
}