Difference Between Switch Cases "@Unknown Default" and "Default" in Swift 5

Using @unknown default in swift 5 enum : How to suppress Default will never be executed warning?

The warning is probably somewhat misleading as the spec says (emphasis added):

A nonfrozen enumeration is a special kind of enumeration that may gain new enumeration cases in the future—even after you compile and ship an app. Switching over a nonfrozen enumeration requires extra consideration. When a library’s authors mark an enumeration as nonfrozen, they reserve the right to add new enumeration cases, and any code that interacts with that enumeration must be able to handle those future cases without being recompiled. Only the standard library, Swift overlays for Apple frameworks, and C and Objective-C code can declare nonfrozen enumerations. Enumerations you declare in Swift can’t be nonfrozen.

So it's not so much that the branch will never be executed but that the feature is completely unsupported for your SomeEnumCases user-defined Swift enum.

There seems to be no supported way of doing what you wish in Swift 5, and some indications that adding cases is seen as a breaking change as it could/would break binary compatibility, but Swift is an ever moving target...

Why does @unknown default (Swift 5) cause compile error?

The error in the middle image looks like a bug in swift and it could be solved by adding a semicolon at the end of the return statement.

In general the compiler is expecting @unknown default to be the last case. Check @unknown documentation from apple where they explain why it must be used with the last case in a switch and more on the "unknown patterns" link in the following quote:

@unknown may only be applied to default or a case consisting of the
single pattern _. Even in the latter case, @unknown must be used with
the last case in a switch
. This restriction is discussed further in
the "unknown patterns" section under "Future directions".

How to work around swift requirement that switch default statement has to be last (after cases)

Because defaults come last, common logic has to come afterwards…

let multiplier: Double = {
switch colorScheme {
case .light:
break
case .dark:
return 0.1
@unknown default:
assertionFailure()
}

return 0.3
} ()

…or be defined early and referred to in later usage.

let multiplier: Double
let lightMultiplier = 0.3
switch colorScheme {
case .light:
multiplier = lightMultiplier
case .dark:
multiplier = 0.1
@unknown default:
assertionFailure()
multiplier = lightMultiplier
}

New Swift 5 warnings for Objective-C enums: how to get rid of them?

TL;DR

If you want Objective-C enums to be treated just like Swift ones, you now need to declare them using a different macro, NS_CLOSED_ENUM, versus the old NS_ENUM. Changing this will make the warning disappear.

The example above would become

typedef NS_CLOSED_ENUM(NSUInteger, CardColor) {
CardColorBlack,
CardColorRed
};

Deets

From the Swift 5 release notes:

In Swift 5 mode, switches over enumerations that are declared in Objective-C or that come from system frameworks are required to handle unknown cases—cases that might be added in the future, or that may be defined privately in an Objective-C implementation file. Formally, Objective-C allows storing any value in an enumeration as long as it fits in the underlying type. These unknown cases can be handled by using the new @unknown default case, which still provides warnings if any known cases are omitted from the switch. They can also be handled using a normal default case.

If you’ve defined your own enumeration in Objective-C and you don’t need clients to handle unknown cases, you can use the NS_CLOSED_ENUM macro instead of NS_ENUM. The Swift compiler recognizes this and doesn’t require switches to have a default case.

Exhaustive condition of switch case in Swift

Swift only truly verifies that a switch block is exhaustive when working with enum types. Even a switching on Bool requires a default block in addition to true and false:

var b = true
switch b {
case true: println("true")
case false: println("false")
}
// error: switch must be exhaustive, consider adding a default clause

With an enum, however, the compiler is happy to only look at the two cases:

enum MyBool {
case True
case False
}

var b = MyBool.True
switch b {
case .True: println("true")
case .False: println("false")
}

If you need to include a default block for the compiler's sake but don't have anything for it to do, the break keyword comes in handy:

var b = true
switch b {
case true: println("true")
case false: println("false")
default: break
}

Detect current device with UI_USER_INTERFACE_IDIOM() in Swift

When working with Swift, you can use the enum UIUserInterfaceIdiom, defined as:

enum UIUserInterfaceIdiom : Int {
case unspecified

case phone // iPhone and iPod touch style UI
case pad // iPad style UI (also includes macOS Catalyst)
}

So you can use it as:

UIDevice.current.userInterfaceIdiom == .pad
UIDevice.current.userInterfaceIdiom == .phone
UIDevice.current.userInterfaceIdiom == .unspecified

Or with a Switch statement:

    switch UIDevice.current.userInterfaceIdiom {
case .phone:
// It's an iPhone
case .pad:
// It's an iPad (or macOS Catalyst)

@unknown default:
// Uh, oh! What could it be?
}

UI_USER_INTERFACE_IDIOM() is an Objective-C macro, which is defined as:

#define UI_USER_INTERFACE_IDIOM() \ ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] ? \ [[UIDevice currentDevice] userInterfaceIdiom] : \ UIUserInterfaceIdiomPhone)

Also, note that even when working with Objective-C, the UI_USER_INTERFACE_IDIOM() macro is only required when targeting iOS 3.2 and below. When deploying to iOS 3.2 and up, you can use [UIDevice userInterfaceIdiom] directly.

use @main in Xcode 12

This might depend on other project code, but the following tested as works (Xcode 12b), so might be helpful.

The idea is to hide one wrapper inside another structure with availability checker:

@available(iOS 14.0, macOS 10.16, *)
struct Testing_SwiftUI2AppHolder {
@main
struct Testing_SwiftUI2App: App {

var body: some Scene {
WindowGroup {
ContentView()
}
}
}
}

Switch Case handling two different scenarios

You can write a properly nested switch like this:

switch completion {
case .failure(let error as CustomError):
switch error {
case .cFailed:
// show alert here
break
case .nFailed:
// show alert here
break
default:
// show alert here
break
}
case .failure(let error as ClassError):
switch error {
case .gFailed:
print("ClassError.gFailed")
default:
// show alert here
break
}
default:
break
}

Or else, you can write a single level switch:

switch completion {
case .failure(CustomError.cFailed):
//...
break
case .failure(CustomError.nFailed):
//...
break
case .failure(ClassError.gFailed):
print("ClassError.gFailed")
default:
break
}


Related Topics



Leave a reply



Submit