Difference between associated and raw values in swift enumerations
Raw values are for when every case in the enumeration is represented by a compile-time-set value. The are akin to constants, i.e.
let A = 0
let B = 1
is similar to:
enum E: Int {
case A // if you don't specify, IntegerLiteralConvertible-based enums start at 0
case B
}
So, A
has a fixed raw value of 0
, B
of 1
etc set at compile time. They all have to be the same type (the type of the raw value is for the whole enum, not each individual case). They can only be literal-convertible strings, characters or numbers. And they all have to be distinct (no two enums can have the same raw value).
Associated values are more like variables, associated with one of the enumeration cases:
enum E {
case A(Int)
case B
case C(String)
}
Here, A
now has an associated Int
that can hold any integer value. B
on the other hand, has no associated value. And C
has an associated String
. Associated types can be of any type, not just strings or numbers.
Any given value of type E
will only ever hold one of the associated types, i.e. either an Int
if the enum is an A
, or a String
if the enum is a C
. It only needs enough space for the bigger of the two. Types like this are sometimes referred to as "discriminated unions" – a union being a variable that can hold multiple different types, but you know (from the enum case) which one it is holding.
They can even be generic. The most common example of which is Optional
, which is defined like this:
enum Optional<T> {
case .Some(T)
case .None
}
How to archive enum with an associated value?
The main problem for your issue is that you cannot pass Swift enums to encode(_:forKey:)
.
This article shown by Paulw11 will help you solve this part. If the enum can easily have rawValue
, it's not too difficult.
But, as you see, Enum with raw type cannot have cases with arguments.
Simple enums can easily have rawValue
like this:
enum UnicornColor: Int {
case yellow, pink, white
}
But enums with associate values, cannot have rawValue
in this way. You may need to manage by yourself.
For example, with having inner enum's rawValue
as Int
:
enum Creature: Equatable {
enum UnicornColor: Int {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
static func == (lhs: Creature, rhs: Creature) -> Bool {
//...
}
}
You can write an extension for Dream.Creature
as:
extension Dream.Creature: RawRepresentable {
var rawValue: Int {
switch self {
case .unicorn(let color):
return 0x0001_0000 + color.rawValue
case .crusty:
return 0x0002_0000
case .shark:
return 0x0003_0000
case .dragon:
return 0x0004_0000
}
}
init?(rawValue: Int) {
switch rawValue {
case 0x0001_0000...0x0001_FFFF:
if let color = UnicornColor(rawValue: rawValue & 0xFFFF) {
self = .unicorn(color)
} else {
return nil
}
case 0x0002_0000:
self = .crusty
case 0x0003_0000:
self = .shark
case 0x0004_0000:
self = .dragon
default:
return nil
}
}
}
(In fact, it is not an actual rawValue
and you'd better rename it for a more appropriate name.)
With a definition like shown above, you can utilize the code shown in the link above.
How can I use sealed classes to describe a finite set of cases with associated values, and a smaller set of such values?
Is this how I should approach this problem?
IMHO, yes, but with some semantic changes, listed after.
How can I best make the properties of the associated value accessible from the sealed class?
You can generate extensions or instance functions for each subclass.
e.g.
val ContentType.Case2.id: String get() = value.id
In this way, you can successfully call:
is ContentType.Case2 -> println(content.id)
How can I reduce the app size while preserving the semantic of another case?
You can do it generating only one class for all the cases which need the same types as parameters and using Kotlin contracts
to handle them.
Taking your example, you could generate:
sealed class ContentType {
class Case1(val value: Foo) : ContentType()
class Case2_3(val value: Bar, val caseSuffix: Int) : ContentType()
}
As you can see, the classes Case2
and Case3
are now only one class and caseSuffix
identifies which one of them it is.
You can now generate the following extensions (one for each case):
@OptIn(ExperimentalContracts::class)
fun ContentType.isCase1(): Boolean {
contract {
returns(true) implies (this@isCase1 is ContentType.Case1)
}
return this is ContentType.Case1
}
@OptIn(ExperimentalContracts::class)
fun ContentType.isCase2(): Boolean {
contract {
returns(true) implies (this@isCase2 is ContentType.Case2_3)
}
return this is ContentType.Case2_3 && caseSuffix == 2
}
@OptIn(ExperimentalContracts::class)
fun ContentType.isCase3(): Boolean {
contract {
returns(true) implies (this@isCase3 is ContentType.Case2_3)
}
return this is ContentType.Case2_3 && caseSuffix == 3
}
Since you are using contracts
the client can now use them with:
when {
content.isCase1() -> println(content.title)
content.isCase2() -> println(content.id)
content.isCase3() -> println(content.id)
}
As you can see, a further optimization could be removing the property caseSuffix
for cases with only one suffix to avoid unnecessary properties.
Related Topics
How to Loop Through View Outlets in a Uiviewcontroller with Swift
How to Create a Fixed-Size Array of Objects
Dispatchsourcetimer and Swift 3.0
Intrinsiccontentsize() - Method Does Not Override Any Method from Its Superclass
How to Do If Pattern Matching with Multiple Cases
Swift Watchos 2 - Cmsensordatalist
Array of Nested Type: Why Does the Compiler Complain
Property Observers Willset and Didset; Property Getters and Setters
Deep Copy for Array of Objects in Swift
Aws Cognito Swift Credentials Provider "Logins Is Deprecated: Use Awsidentityprovidermanager"
How to Make Class Methods/Properties in Swift
Programmatically Navigate to New View in Swiftui
One-Line Closure Without Return Type
Variable 'Xxx' Was Never Mutated, Consider Changing to 'Let'
Swift Setter Causing Exc_Bad_Access
Trying to Know When a Window Closes in a MACos Document Based Application