Swift — @unknown and @frozen attributes

Jullian Mercier
2 min readAug 21, 2019

--

Introduced with Swift 5, the @unknown attribute is particularly useful when switching over a nonfrozen enumeration.

While the @frozen attribute is used on familiar enums such as Result or Optional within the standard library..

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)

}

..some enums such as DecodingError are declared without the keyword making them nonfrozen.

public enum DecodingError : Error {
public struct Context {
public let codingPath: [CodingKey]
public let debugDescription: String
public let underlyingError: Error?
public init(
codingPath: [CodingKey],
debugDescription: String,
underlyingError: Error? = nil
)
}
case typeMismatch(Any.Type, DecodingError.Context)
case valueNotFound(Any.Type, DecodingError.Context)
case keyNotFound(CodingKey, DecodingError.Context)
case dataCorrupted(DecodingError.Context)
...
}

While the difference is subtle it is key to understanding how the @unknown attribute eventually works.

— frozen vs. nonfrozen

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

Nonfrozen enums can only be defined by library authors or within the standard library itself.

Only the standard library, Swift overlays for Apple frameworks, and C and Objective-C code can declare nonfrozen enumerations.

A frozen enum may not gain new cases in the future.

When switching over the Optional enum , we do not need to explicitly handle possible future cases.

switch foo {
case .some(_):
break
case .none:
break
}

Since it is a frozen one and our switch case is exhaustive, the compiler won’t produce any warnings.

Trying to add an extra @unknown default case would make the compiler complain as follows.

// Case is already handled by previous patterns; consider removing it
// Default will never be executed

This is a common use case. Same goes for enumerations declared in Swift since they can’t be nonfrozen.

The Swift standard library is composed of nonfrozen enums such as Mirror.AncestorRepresentation and some frameworks such as CoreLocation composed with a CLAuthorizationStatus enum.

let status = CLAuthorizationStatus.notDeterminedswitch status {
case .notDetermined:
print(“Status is not determined yet”)
case .restricted:
print(“Status is restricted”)
case .denied:
print(“Status is denied”)
case .authorizedAlways:
print(“Status is always authorized”)
case .authorizedWhenInUse:
print(“Status is authorized only when in use”)
@unknown default:
print(“Some authorization status unknown when this code was
compiled”)
}

When switching over a nonfrozen enum where all cases have been covered such as in above example, the compiler will accept a default unknown case to handle all possible future cases from the (nonfrozen) enum.

The usage of @unknown enable the compiler to let us know whenever a new case (not yet covered) has been added.

« This future warning informs you that the library author added a new case to the enumeration that doesn’t have a corresponding switch case. »

Conclusion

Using the @unknown attribute on default case comes in handy whenever a new case is added to a nonfrozen enum so we can get the information through the compiler and handle it properly.

Follow me on Twitter for my latest articles.

--

--

Jullian Mercier
Jullian Mercier

Written by Jullian Mercier

Senior iOS engineer. jullianmercier.com. @jullian_mercier. Currently looking for new job opportunities.

Responses (1)