Using Alamofire we're trying to determine if an error is a certain kind of error (response code 499) as represented by a "nested" AFError enum:
if response.result.isFailure {
if let aferror = error as? AFError {
//THIS LINE FAILS
if (aferror == AFError.responseValidationFailed(reason: AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: 499))) {
....
}
}
}
But this results in the compiler error:
Binary operator '==' cannot be applied to two 'AFError' operands
How can you do this?
Well, you could trying extending AFEError to conform to Equatable in order to use ==, but you are probably better off using switch and pattern matching:
switch aferror {
case .responseValidationFailed(let reason) :
switch reason {
case AFError.ResponseValidationFailureReason.unacceptableStatusCode(let code):
if code == 499 { print("do something here") }
default:
print("You should handle all inner cases")
{
default:
print("Handle other AFError cases")
}
This is the best syntax to ensure (and get the compiler to help you ensure) that all possible error cases and reasons are handled. If you only want to address a single case, like in your example, you can use the newer if case syntax, like this:
if case .responseValidationFailed(let reason) = aferror, case AFError.ResponseValidationFailureReason.unacceptableStatusCode(let code) = reason, code == 499 {
print("Your code for this case here")
}
As I point out here, you cannot, by default, apply the equality (==) operator between cases of an enum that as an associated value (on any of its cases); but there are many other ways to find out whether this is the desired case (and, if there is an associated value, to learn what the associated value may be).
Related
I have an enum with associated values:
enum SessionState {
...
case active(authToken: String)
...
}
I can use case let to compare enum cases with associated values:
if case .active = session.state {
return true
} else {
return false
}
But can I directly return the case as a bool expression? Something like:
// Error: Enum 'case' is not allowed outside of an enum
return (case .active = session.state)
A simple comparison doesn’t work either:
// Binary operator '==' cannot be applied to operands of type 'SessionState' and '_'
return (session.state == .active)
Unfortunately, you cannot (directly) use a case condition as a Bool expression. They are only accessible in statements such as if, guard, while, for etc.
This is detailed in the language grammar, with:
case-condition → case pattern initializer
This is then used in a condition:
condition → expression | availability-condition | case-condition | optional-binding-condition
(where expression represents a Bool expression)
This is then used in condition-list:
condition-list → condition | condition , condition-list
which is then used in statements such as if:
if-statement → if condition-list code-block else-clause opt
So you can see that unfortunately case-condition is not an expression, rather just a special condition you can use in given statements.
To pack it into an expression, you'll either have to use an immediately-evaluated closure:
return { if case .active = session.state { return true }; return false }()
Or otherwise write convenience computed properties on the enum in order to get a Bool in order to check for a given case, as shown in this Q&A.
Both of which are quite unsatisfactory. This has been filed as an improvement request, but nothing has come of it yet (at the time of posting). Hopefully it's something that will be possible in a future version of the language.
No, at this moment.
but may implement in the future.
https://forums.swift.org/t/pitch-case-expressions-for-pattern-matching/20348
I have a code snippet here that works but am curious if there is a cleaner way to accomplish the same thing. I haven't seen anything exactly like this so far.
Logic I want to Achieve
The error is nil or is not a SpecialError
The error is non-nil BUT .foo() returns false
Code
enum SpecialError: Error {
func foo() -> Bool
}
let error: Error? // Some optional Error is passed in
if let myError = error as? SpecialError, myError.foo() {
// Don't care about this case
} else {
// This is the case I'm interested in
bar()
}
I'm curious if there is a better way to accomplish this if let else logic.
I may be misunderstanding, but it seems like nothing happens in the first branch of the if statement and you want to cut it down to just the second part? In that case, this should work for you:
if !((error as? SpecialError)?.foo() ?? false) {
bar()
}
This will execute bar() if:
1. error is nil
2. error is not a SpecialError
3. foo() returns false
The condition you want is for when the expression (error as? SpecialError)?.foo() evaluates to either:
nil, in which case error is not a SpecialError, or is nil.
false, in which case error is a SpecialError, but foo() returned false.
In your case, one way to express this is by taking advantage of the fact that the equality operators are overloaded for optionals, and saying:
if (error as? SpecialError)?.foo() != true {
bar()
}
As we're using the overload of != that compares optionals, true will be promoted to a Bool?, so we're checking that (error as? SpecialError)?.foo() is not a .some(true), which in this case is equivalent to checking if it is .some(false) or .none.
If I understand your question correctly, you're probably looking for something like this:
if !((error as? SpecialError)?.foo() ?? false) {
How about translating it exactly as you explained it:
if error == nil || !(error! is SpecialError) || !error!.foo() {
bar()
}
The short-circuiting of or will prevent the force unwraps of error from being a problem.
I think your code is hard to read, and John Montgomery's code is even harder to read and I predict it will be hard to maintain: imagine another developer looking at this code a year from now and asking you what it is doing, or even worse, that developer cannot ask you as you are no longer available. Imagine yourself looking at this code even even a couple months from now.
if let myError = error as? SpecialError, myError.foo() is convoluted, perhaps being a bit too clever. It contains too much logic to be long term readable by a team of developers.
Your first block could just check if myError is nil
Your second block could just check if is myError of type SpecialError
//you could use a guard statement here
if myError == nil {
return;
}
if let myError = error as? SpecialError {
//ok myError is type SpecialError it has a foo method I can call
if(myError.foo()) {
//do something
} else {
//do something else
}
} else { //myError not type SpecialError and is not nil
// This is the case I'm interested in
bar()
}
if let theFoo = (error as? SpecialError)?.foo(){
if theFoo != true{
bar()
} //in case you want to do handling if theFoo is true
} //in case you want to do handling if error is not SpecialError
UPDATE This is fixed in Swift 3.1
In migrating an if-else to a switch statement, I noticed that type inference wasn't working. Why do I need to specify HKQuantityTypeIdentifier in each case when quantityTypeIdentifier is already of that type?
func process(samples: [HKSample]?, quantityTypeIdentifier: HKQuantityTypeIdentifier) {
DispatchQueue.main.async { [weak self] in
if let quantitySamples = samples as? [HKQuantitySample] {
for sample in quantitySamples {
switch quantityTypeIdentifier {
case HKQuantityTypeIdentifier.distanceWalkingRunning:
// code
case HKQuantityTypeIdentifier.activeEnergyBurned:
// code
case HKQuantityTypeIdentifier.heartRate:
// code
default:
fatalError("Quantity Type Identifier not implemented \(quantityTypeIdentifier)")
}
}
}
}
}
I am able to call the function like:
process(samples: samples, quantityTypeIdentifier: .distanceWalkingRunning)
I think you've found a bug, or at least you have a reasonable case to claim one. The inconsistency is nicely shown by a much shorter example:
let c : UIColor = .red
switch c {
case .red : print ("red") // error
default : break
}
That won't compile. You can say .red on the first line but not on the third line. That seems a clear inconsistency.
Now, having said that, I can certainly explain why the rules are different in the two different places. A case expression is resolved according to the ~= operator and the rules of forming a pattern. Those rules are different from anything else in Swift (hence, for example, there are situations where you say as in a case pattern but would say as? everywhere else). So evidently those are the rules that would need tweaking in order for this to work. They have been tweaked so far as to allow bare enum cases but not bare enum-like struct "cases" (that is, static members of structs that are RawRepresentable where those static members evaluate to an instance of the struct itself).
Finally, here's a skanky workaround that I like to use when case patterns become too onerous:
let c : UIColor = .red
switch true {
case c == .red : print ("red") // heh heh
default : break
}
By switching on true and writing out the entire boolean condition we break the bounds of pattern-matching and reenter the world of normal expressions.
When calling a function in Swift 3 that throws, you have to be exhaustive in catching all possible errors, which often means you have an unnecessary extra catch {} at the end to handle errors that won't happen.
Is it possible to say throws MyErrorType so that the compiler can know you have indeed been exhaustive when you handle all cases from that enumeration?
There's no simple way to be type-safe with thrown errors. Consider this, if the compiler allowed you to specify throws MyErrorType, then it would also have to ensure within that function body that you're not trying a function that could potentially throw a different type outside of a do/catch block. (Well there is but it would add layers of unnecessary complexity). The Swift compiler can already be slow and get stuck in loops when inferring types, inferring Thrown types all the way up a chain of throwing functions could be a nightmare.
The running idea is that for most errors you're going to handle them in a small subset of ways anyhow.
That being said, there's no need for you to add extra catch let error as MyErrorType clauses, you can simply use a switch in a catch block like so:
do {
try something()
} catch let e {
switch e {
case let m as MyErrorType: handleMyError(m)
case let o as OtherErrorType: handleOther(o)
case is ThirdErrorType: print("error \(e)")
default: handleElse(e)
}
}
My suggestion for this problem is instead of throwing an error return a Result type in your function. It would be something like this.
enum MyCustomError: Error {
case genericError
}
func operationThatFails() -> Result<Response, MyCustomError> {
guard requiredConsition() else {
return .failure(.genericError)
}
return Response()
}
Then you can handle the error like this:
let result = operationThatFails()
switch result {
case .success(let value):
// handle success
case .failure(let error):
// handle error
}
This way your error is always type safe
Enums in Swift look to be really powerful, but... I must be missing something about how I'm implementing this. I want to define some actions for a remote media player. Seems like a good use case for an enum. I've defined the allowed message types in the Enum, and I'd like to use it to get a modified parameter dictionary. The parameters will eventually get sent as JSON to the player. At the moment, I'm getting a Braced block of statements is an unused closure error. Here's the relevant code:
public enum PlayerCommand {
case Play
case Pause
case Load(String)
func params(cmd_id:NSInteger) -> [String : Any] {
var dict = [
CMD_ID : cmd_id,
TYPE : "LOAD",
AUTOPLAY : false,
MEDIA : NSNull()
]
switch self {
case .Load(let media): {
dict.updateValue(media, forKey: MEDIA)
}
case .Play: {
dict.updateValue("PLAY", forKey: TYPE)
dict[CURRENT_TIME] = NSNull()
}
case .Pause: {
dict.updateValue("PAUSE", forKey: TYPE)
}
default:
}
return dict
}
}
I am sure that there is also a more functional (swiftian?) way to express this, as well, but I'm not having a lot of luck with the syntax yet. map?
You have your switch syntax a bit off, is all. You don’t need { } around the expressions of each case (Swift is interpreting them as you trying to create a closure expression hence the error).
Just do case .Play: dict.updateValue(etc.).
Note also you must have a statement in the default clause – but you don’t actually need a default in this case, since your switch is exhausting all the possibilities.