Non Exhaustive List When Handling Errors Inside a Class Function in Swift
As said by the language guide (emphasis mine):
The
catch
clauses don’t have to handle every possible error that the code in itsdo
clause can throw. If none of thecatch
clauses handle the error, the error propagates to the surrounding scope. However, the error must be handled by some surrounding scope [...]
Therefore, your example
class Board {
class func parse(string: String) {
do {
try Piece.parse(string: string)
} catch Piece.ParseError.unknown(let string) {
print(string)
}
}
}
is illegal – because combined, the do-catch block and the enclosing scope (the actual method itself) don't exhaustively handle every possible error that Piece.parse(string: string)
can throw (remember that a throwing function can throw any error type that conforms to the Error
protocol).
You would either want to add a "catch all" block to your do-catch in order to handle any other thrown error:
do {
try Piece.parse(string: string)
} catch Piece.ParseError.unknown(let string) {
print(string)
} catch {
// do error handling for any miscellaneous errors here.
print(error)
}
Or make parse(string:)
a throwing method in order to propagate any uncaught errors back to the caller.
class func parse(string: String) throws {
// ...
}
The only reason why
enum ParseError: Error {
case unknown(string: String)
}
func parse(string: String) throws {
throw ParseError.unknown(string: string)
}
do {
try parse(string: "rook")
} catch ParseError.unknown(let string) {
print(string)
}
compiles at the top-level of a main.swift file is simply because that scope is special. It can catch any uncaught error, and upon doing so will invoke fatalError()
with a description of that error.
I cannot find any official documentation for this, but if you look in the Standard Library's ErrorType.swift file, you'll see the following function:
/// Invoked by the compiler when code at top level throws an uncaught error.
@_silgen_name("swift_errorInMain")
public func _errorInMain(_ error: Error) {
fatalError("Error raised at top level: \(String(reflecting: error))")
}
And if we examine the IR emitted for a simplified version of the above code, sure enough we can see that the compiler inserts a call to swift_errorInMain
upon an uncaught error being thrown.
With a playground, you get a similar behaviour in that the compiler allows uncaught errors at the top-level – although in the case of an error being thrown, the playground just seems to silently terminate without displaying a fatal error message.
It's difficult to investigate further due to the fact that Swift playgrounds run code in their own special environment, therefore meaning that the runtime behaviour can wildly differ from code compiled with swiftc
. Really, you should never use a playground to test the actual behaviour of Swift code.
Swift do-try-catch syntax
There are two important points to the Swift 2 error handling model: exhaustiveness and resiliency. Together, they boil down to your do
/catch
statement needing to catch every possible error, not just the ones you know you can throw.
Notice that you don't declare what types of errors a function can throw, only whether it throws at all. It's a zero-one-infinity sort of problem: as someone defining a function for others (including your future self) to use, you don't want to have to make every client of your function adapt to every change in the implementation of your function, including what errors it can throw. You want code that calls your function to be resilient to such change.
Because your function can't say what kind of errors it throws (or might throw in the future), the catch
blocks that catch it errors don't know what types of errors it might throw. So, in addition to handling the error types you know about, you need to handle the ones you don't with a universal catch
statement -- that way if your function changes the set of errors it throws in the future, callers will still catch its errors.
do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
print("Not me error")
} catch SandwichError.DoItYourself {
print("do it error")
} catch let error {
print(error.localizedDescription)
}
But let's not stop there. Think about this resilience idea some more. The way you've designed your sandwich, you have to describe errors in every place where you use them. That means that whenever you change the set of error cases, you have to change every place that uses them... not very fun.
The idea behind defining your own error types is to let you centralize things like that. You could define a description
method for your errors:
extension SandwichError: CustomStringConvertible {
var description: String {
switch self {
case NotMe: return "Not me error"
case DoItYourself: return "Try sudo"
}
}
}
And then your error handling code can ask your error type to describe itself -- now every place where you handle errors can use the same code, and handle possible future error cases, too.
do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch let error as SandwichError {
print(error.description)
} catch {
print("i dunno")
}
This also paves the way for error types (or extensions on them) to support other ways of reporting errors -- for example, you could have an extension on your error type that knows how to present a UIAlertController
for reporting the error to an iOS user.
Try Block throwing an error
Change to the following.. for general catch of all errors:
if WCSession.isSupported() {
if session.watchAppInstalled {
let UserInfo = ["waste":floatWastedAmount]
do {
try session.updateApplicationContext(UserInfo)
} catch {
print("Updating the context failed")
}
}
}
Enclosing Catch is not Exhaustive (Audio Recording)
The error message is misleading. A
catch let error1 as NSError
is exhaustive, because all errors are bridged to NSError
automatically.
It seems that the compiler is confused by the forced cast
recordSettings as! [String : AnyObject]
and that causes the wrong error message. The solution is to create the
settings dictionary with the correct type in the first place:
let recordSettings: [String: AnyObject] = [
AVFormatIDKey : Int(kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.Max.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey : 2,
AVSampleRateKey : 44100.0 ]
var error: NSError?
do {
soundRecorder = try AVAudioRecorder(URL: getFileURL(), settings: recordSettings)
} catch let error1 as NSError {
error = error1
soundRecorder = nil
}
Swift 2.1 - Error Handling - Multiple Catch is not compiled
You need to include a default catch block (much like when using a switch case) to make your error handling exhaustive; in case the error thrown is not one of the ones you specify.
func catchingSpecificError() {
do {
try assignName(nil) // Compiler Error displays at this line.
}catch PersonError.IsNotAPerson {
print("Propagated error is caught in catch on case .NotAPerson")
}catch {
print("default..")
}
}
Slightly off-topic, but I assume personName = name
refers to a class property personName
that we cannot see in your example above.
With the default catch
block added, you mention in the comments below that function catchingSpecificError()
does not cast the error you expect; or rather, the default catch
block catches your error.
Now, since I don't know the context of your code, I cannot infer what is actually going wrong in your case. I'll post a working example for your below where---in the context of this question---the throw and catch work as expected. Note however that your use of the guard
block is somewhat out of by convention. Usually you make use of guard
just like if let
blocks, i.e., guard let name = name else { ..
, which will enter the guard
block if name
contains nil
.
Anyway, consider the following fully working example:
enum PersonError: ErrorType {
case IsNotAPerson
case IsNotAGoodPerson
case IsNotAValidPerson
}
class Person {
var personName : String? = ""
func assignName(name: String?) throws {
guard name != nil else {
throw PersonError.IsNotAPerson
}
personName = name
}
func catchingSpecificError() {
do {
try assignName(nil)
}catch PersonError.IsNotAPerson {
print("Propagated error is caught in catch on case .NotAPerson")
}catch {
print("default..")
}
}
}
var myPerson = Person()
var myName : String? = nil
myPerson.catchingSpecificError()
/* Prints: "Propagated error is caught in catch on case .NotAPerson" */
As expected, we catch PersonError.IsNotAPerson
thrown by function assignName
. Hopefully you can make use of this example your get your own code (the parts that you haven't shown us in your question) working.
Swift 2.0 error handling
Like the error states, you need to add a final catch
without constraints.
Create a catch/replace error counterpart that does not complete the publisher?
An error completing the pipeline is part of the contract by which publishers and subscribers work. And this makes sense. If an upstream publisher throws an error and doesn't know how to recover on its own, it's basically done. It's not unlike a function throwing an error.
In your example, tryMap
throws an error, and although a downstream like replaceError
can transform the error for its downstream, it does not expect any more values from its upstream, so it too completes.
flatMap
shields the rest of the pipeline from the error-throwing publisher. In other words, the upstream of flatMap
doesn't throw an error, and flatMap
itself doesn't throw an error since the returned inner publisher (the pipeline with Just
/tryMap
/catch
) handles the error.
Just([1, 2, 3, 5, 6])
.flatMap {
Just($0)
.tryMap { _ in throw DummyError() }
.catch { _ in Just(4) }
}
.sink { print($0) }
Single try meaning in Swift
In addition to the cases you mentioned, you can call try
at
top-level code. Here is a simple self-contained example:
// main.swift:
enum MyError : Error {
case failed
}
func foo() throws -> Int {
throw MyError.failed
}
defer { print("Good bye.") }
let x = try foo()
print(x)
You can compile and run this as a Xcode "Command Line Project"
or directly from the command line:
$ swiftc main.swift
$ ./main
Good bye.
Fatal error: Error raised at top level: main.MyError.failed: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.74.1/src/swift/stdlib/public/core/ErrorType.swift, line 187
Illegal instruction: 4
The failed try
in the top-level code causes the program to
terminate with an error message. Deferred statement (if present) will be executed however.
This is slightly different from using a forced try!
statement,
which causes the program to abort as well, but immediately, without executing deferred statements. (This can be relevant if deferred
statements are used to clean-up resources, e.g. remove temporary files).
The error message originates from ErrorType.swift, line 187:
/// Invoked by the compiler when code at top level throws an uncaught error.
@_inlineable // FIXME(sil-serialize-all)
@_silgen_name("swift_errorInMain")
public func _errorInMain(_ error: Error) {
fatalError("Error raised at top level: \(String(reflecting: error))")
}
(also observed in Non Exhaustive List When Handling Errors Inside a Class Function in Swift).
Apparently the top-level code behaves as if embedded in a
do-catch block:
do {
func foo() throws -> Int {
throw NSError(domain: "foo", code: 0, userInfo: nil)
}
defer { print("Good bye.") }
let x = try foo()
} catch {
fatalError("Error raised at top level: \(String(reflecting: error))")
}
Related Topics
Float Is Not Convertible to 'Mirrordisposition' Swift What Is Mirrordisposition
Converting Audiobuffer to Cmsamplebuffer with Accurate Cmtime
Swift Find Superview of Given Class with Generics
Adding a Custom Font to MACos App Using Swift
How to Remove Top Space of 'Form' in Swiftui
Subscript of a Struct Doesn't Set Values When Created as an Implicitly Unwrapped Optional
Workarounds for Generic Variable in Swift
Ios/Tvos Playground Fails with "Unable to Find Execution Service for Selected Run Destination"
Nsdateformatter Detect 24-Hour Clock in Os X and iOS
How to Create an Uppercase Version of a String in Swiftui
Differenceand Purpose of Auto and Escaping Closure in Swift
Swift Progress View with Nstimer
Xcode 8 Does Full Project Rebuild
Macos App Local Notification Not Showing When Testing with Xcode