Swift generic problem with array and individual item generic support - swift

I have problem with declaring an Array and initialize it with many different generic items and in the same time use those items individually with generic support. Lets look at this:
protocol Item {
associatedtype ValueType: Any
var value: ValueType? { get set }
}
class ItemClass<T: Any>: Item {
typealias ValueType = T
var value: ValueType?
}
let intItem = ItemClass<Int>()
let stringItem = ItemClass<String>()
let array: [ItemClass<Any>] = [intItem, stringItem]
// Iterate over items and use `value` property as Any?
for item in array {
let val: Any? = item.value
// do something with val
}
// Individual usage with generic types support
let intValue: Int? = intItem.value
let stringValue: String? = stringItem.value
Why there is an error in array declaration like this:
Cannot convert value of type 'ItemClass<Int>' to expected element type 'ItemClass<Any>'

This entire approach is incorrect, and you can see that by the consuming code:
// Iterate over items and use `value` property as Any?
for item in array {
let val: Any? = item.value
// do something with val
}
In that "do something with val," what can you possibly do? There are no methods on Any. If you're going to do something like as? T, then you've broken the whole point of the types, because you don't mean "any". You mean "some list of types I know about." If you want "some list of types I know about," that's an enum with associated data, not a protocol with an associated type.
enum Item {
case string(String)
case int(Int)
var stringValue: String? {
guard case .string(let value) = self else { return nil }
return value
}
var intValue: Int? {
guard case .int(let value) = self else { return nil }
return value
}
}
let intItem = Item.int(4)
let stringItem = Item.string("value")
let array: [Item] = [intItem, stringItem]
// Iterate over items and use `value` property as Any?
for item in array {
switch item {
case let .string(value): break // Do something with string
case let .int(value): break // Do something with int
}
}
// Individual usage with generic types support
let intValue: Int? = intItem.intValue
let stringValue: String? = stringItem.stringValue
If, on the other hand, you really mean "any type," then you're not going to be able to put them in a collection without hiding the values in a box that gets rid of any information about that type (i.e. "a type eraser"). Which you need comes down to your actual use case. There isn't a single answer; it's going to be driven by how you want to consume this data.
But if you need as? very much at all, you've done something wrong.

Related

Is there a way to compile with a generic type?

This is something that has vexed a number of developers including myself. Let say we have a protocol that defines a subscript which we apply to a simple class.
protocol Cache {
subscript<Value>(_: String) -> Value? { get set }
}
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript<Value>(key: String) -> Value? {
get {
cache[key] as? Value
}
set {
if let value = newValue {
cache[key] = value
} else {
cache.remove(key)
}
}
}
}
This works fine as long as we know the types:
cache["abc"] = 5
let x: Int? = cache["abc"]
but the developers want to do this:
cache["abc"] = nil
Which won't compile because the compiler cannot determine the Value generic type. This works however
cache["abc"] = nil as String?
I've tried a number of things but they all have drawbacks. Things like adding a second subscript with the Any type. Nothing seems to work well even though it would seem like a simple problem.
Has anyone found a solution that handles cache["abc"] = nil?
You can do this by changing your protocol requirements somewhat.
Have the protocol require a subscript that does not use generics, and returns an Any?.
protocol Cache {
subscript(key: String) -> Any? { get set }
}
This subscript will let you do the following:
cache["abc"] = 5
cache["abc"] = nil
let value = cache["abc"] // value is an `Any?`
but it will not let you do this:
let number: Int? = cache["abc"] // error
So, let's fix that by adding another subscript to Cache. This subscript is equivalent to your original subscript requirement, except it doesn't need a setter and will call the other subscript (the one required by the protocol):
extension Cache {
subscript<Value>(key: String) -> Value? {
self[key] as? Value
}
}
(If you're worried that this subscript calls itself, don't be. self[key] here actually calls the other subscript, not this one. You can confirm this in Xcode by command-clicking on the [ or the ] in self[key] to jump to the definition of the other subscript.)
Then, implement the required subscript in your class:
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript(key: String) -> Any? {
get { cache[key] }
set { cache[key] = newValue }
}
}
This will allow all of the following to compile:
let cache = InMemoryCache()
cache["abc"] = 5
let x: Int? = cache["abc"]
cache["abc"] = nil
There is a workaround to have your desire output.
Because this is a dictionary so you get assign nil directly in your InMemoryCache
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript<Value>(key: String) -> Value? {
get {
cache[key] as? Value
}
set {
if let value = newValue {
cache[key] = value
} else {
cache[key] = nil // make nil directly here
}
}
}
}
In here because of Value is a generic type. So you can not assign nil directly. It must have a specific type.
Instead you can do like this
let nilValue : Int? = nil // any type nil you want
cache["abc"] = nilValue
or directly cast it to nil of any tupe before assign to dictionary
cache["abc"] = (nil as String?)
It will refresh anything value is store in the key.
Example
// value
let nilValue : Int? = nil
var number : Int? = nil
var string : String? = nil
cache["abc"] = 5
number = cache["abc"] // Optional.some(5)
cache["abc"] = "abc"
number = cache["abc"] // nil
string = cache["abc"] // Optional.some("abc")
cache["abc"] = nilValue
number = cache["abc"] // nil
string = cache["abc"] // nil
The reason why you are having a hard time with this is because
cache["abc"] = nil
cannot be compiled. There is not enough information to infer the generic type of the subscript - or of the optional value. The compiler sees something like
cache<?>["abc"] = Optional<?>.none
How is it supposed to figure out what to put in place of the question marks?
There's another ambiguity. Your cache can contain any type, even Optional. When you are assigning nil to the subscript, how does anybody know if you want to remove the element or store an instance of Optional<Something>.none at the subscript?
When I find myself fighting the language in this way, I usually try to take a step back and ask if I am perhaps doing something fundamentally bad. I think, in this case, the answer is yes. You are trying to pretend something is more strictly typed than it really is.
I think your getter/setter should explicitly take a value that is of type Any. It works better and it has the advantage that it explicitly documents for the user that a Cache conforming type can store anything in it.
For this reason, I would say TylerP's solution is the best. However, I would not create a subscript in the extension, I would define a function
extension Cache
{
func value<Value>(at key: String) -> Value?
{
self[key] as? Value
}
}
The reason for this is that the compiler can get confused when you have multiple subscripts with similar signatures. With the extension above, I can conform Dictionary<String, Any> to the protocol and not need a new class.
extension Dictionary: Cache where Key == String, Value == Any {}
var dict: [String : Any] = [:]
dict["abc"] = 5
let y: Int? = dict.value(at: "abc")
dict["abc"] = nil
Obviously, the above won't be useful to you if you need reference semantics for your cache.
TylerP's solution was pretty much bang on the money. For completeness though, here's what the code now looks like:
protocol Cache {
/// Handles when we need a value of a specific type.
subscript<Value>(_: String) -> Value? { get }
/// Handles getting and setting any value.
/// The getter is rarely used because the generic getter above
/// is used. Setting a value compiles because we don't care what
/// type is it. Setting a `nil` also compiles for the same reason.
subscript(_: String) -> Any? { get set }
}
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript(key: String) -> Any? {
get { cache[key] }
set {
if let value = newValue {
cache[key] = value
} else {
remove(key)
}
}
}
subscript<Value>(key: String) -> Value? {
cache[key] as? Value
}
}

Is there a shorter way of declaring CodingKeys?

Say you have a struct for a model of your API response. Let's say it has 50 members. However, 5-7 members are non-standard casing, you could have AUsernAme or _BTmember, but the rest are all snake case credit_score or status_code.
Rather than writing all members like this:
struct MyStruct {
let aUserName: String
// +50 more...
private enum CodingKeys: String, CodingKey {
case aUserName = "AUsernAme"
// +50 more...
}
}
Is there a way that we can write it like this?
struct MyStruct {
#CodingKey("AUsernAme") let aUserName: String
let creditScore: Int
// +50 more ...
}
Edit: I guess this is not possible with the current Swift version, but does anyone know if this would somehow be included in the future versions of Swift?
The solution which Sweeper provided is a great solution to your problem, but IMO it may display great complexity to your problem and to the next developers who will read this code.
If I were you, I would just stick to writing all the CodingKeys for simplicity. If your worry is writing a lot of lines of cases, you can write all the cases that doesn't need custom keys in one line and just add the keys with unusual/non-standard casing on new lines:
case property1, property2, property3, property4, property5...
case property50 = "_property50"
And since you mentioned that the rest are in snake case, not sure if you know yet, but we have JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase.
Hope this helps `tol! :)
How about setting a custom keyDecodingStrategy just before you decode instead?
struct AnyCodingKey: CodingKey, Hashable {
var stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
}
let mapping = [
"AUsernAme": "aUserName",
// other mappings...
]
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let key = codingPath[0].stringValue
guard let mapped = mapping[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
})
This assumes your JSON has a single level flat structure. You can make this into an extension:
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [String: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
let key = codingPath[0].stringValue
guard let mapped = dict[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .mappingRootKeys(mapping)
If your JSON has more levels, you can change the type of the dictionary to [JSONPath: String], where JSONPath is a type that you can create that represents a key in a nested JSON. Then add a bit of code that converts the coding path, which is just an array of coding keys, to JSONPath. This should not be hard to write on your own.
A simple way is to just use [AnyCodingKey] as JSONPath, but there are many other ways too, and I encourage you to experiment and find the one you like the best.
typealias JSONPath = [AnyCodingKey]
extension AnyCodingKey {
init(codingKey: CodingKey) {
if let int = codingKey.intValue {
self.init(intValue: int)
} else {
self.init(stringValue: codingKey.stringValue)
}
}
}
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [JSONPath: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
guard let mapped = dict[codingPath.map(AnyCodingKey.init(codingKey:))] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let mapping = [
[AnyCodingKey(stringValue: "AUsernAme")]: "aUserName"
]
It is not possible to use a property wrapper for this. Your property wrapper #CodingKey("AUsernAme") let aUserName: String will be compiled to something like this (as per here):
private var _aUserName: CodingKey<String> = CodingKey("AUsernAme")
var aUserName: String {
get { _aUserName.wrappedValue }
set { _aUserName.wrappedValue = newValue }
}
There are two main problems with this:
Assuming you don't want to write init(from:) for all the 50+ properties in MyStruct, code will be synthesised to decode it, assigning to its _aUserName property. You only have control over the init(from:) initialiser of the CodingKey property wrapper, and you cannot do anything about how MyStruct is decoded in there. If MyStruct is contained in another struct:
struct AnotherStruct: Decodable {
let myStruct: MyStruct
}
Then you can indeed control the coding keys used to decode myStruct by marking it with a property wrapper. You can do whatever you want in the decoding process by implementing the property wrapper's init(from:), which brings us to the second problem:
The coding key you pass to the CodingKey property wrapper is passed via an initialiser of the form init(_ key: String). But you control the decoding via the initialiser init(from decoder: Decoder) because that is what will be called when the struct is decoded. In other words, there is no way for you to send the key mappings to the property wrapper.

Swift : how to list fields in a struct from Type object?

I want to extract a list of the fields in a struct, having at my disposal just the Type object.
public struct CoordinateData: Codable {
var longitude: Double?
var latitude: Double?
}
I receive Codable.Type (at this stage, I do not know the object is CoordinateData and I want to keep my solution generic for any Codable)
I want to extract ["longitude", "latitude"] ([String])
I tried to use Mirror and it works when I know the exact type of the object and when the type as an empty initializer:
let metatype: CoordinateData.Type = CoordinateData.self
let c3 = metatype.init()
let m3 = Mirror(reflecting: c3)
for (property, value) in m3.children {
guard let property = property else {
print("no proeprty")
continue
}
print(property)
}
But this solution does not work when all I have is Codable.Type because it has only one .init() method that expect a decoder .init(from: Decoder) which I don't have.
Any suggestion ?
This could work, But you need to consider a few things.
func mirror<T: Any>(object: T) {
let c3 = object
let m3 = Mirror(reflecting: c3)
for (property, _) in m3.children {
guard let property = property else {
print("no proeprty")
continue
}
print(property)
}
}
Use: mirror(object: CoordinateData())
mirror(object: Foo())
You always need to pass an initialized object.
That would remove the init responsibility from the function because as far as i know, you can't initialize the generic objects because they're generic.
So bypassing the object itself this would work for you.
Update: since there is no explicit struct type parameter this could be misused also but passing any argument that's not an object it wouldn't give any result, this could be solved by changing the structs to classes and pass <T> as <T: AnyObject>.
You can alternatively do it this way too, since Mirror parameter is already Any type, so we can extension the Mirror it self to make a getProperties() function check below.
extension Mirror {
func getProperties() -> [String] {
self.children.compactMap { child in
guard let property = child.label else {
return nil
}
return property
}
}
}
Usage: var list = Mirror(reflecting: Foo()).getProperties()
This will always give you an array of strings, however if there were no properties it would return an empty array.

RealmSwift: Casting Object.subscript AnyObject? to List<>

I’m writing a custom update method to allow control over how values are represented in Realm. The approach taken is to match incoming key: values (from JSON) against objects’ objectSchema.properties and convert the values accordingly. Everything is general-purpose with values stored using Object.subscript.
For to-one relations, where property type is .Object the system can recurse and create or update the appropriate nested object of type determined by Property.objectClassName.
For to-many relations, where property type is .Array we must modify a List of objects of type objectClassName. List however is a generic so that’s List< concrete type of objectClassName >.
As Object.subscript returns values of type AnyObject? we need to cast this into something that can be treated as a list irrespective of its contained type. How can this be achieved?
(I've replaced the example below to better illustrate the problem)
e.g.
typealias ValueDictionary = Dictionary<String, AnyObject>
func update(object: Object, values: ValueDictionary) {
for property in object.objectSchema.properties {
if let value = values[property.name] {
var newValue: AnyObject?
switch property.type {
case .Object:
let objectType = NSClassFromString(
"mymodule" + property.objectClassName) as! Object.Type
newValue = relatedObjectWithType(objectType,
values: value as! ValueDictionary)
case .Array:
let objectType = NSClassFromString(
"mymodule" + property.objectClassName) as! Object.Type
newValue = listOfRelatedObjectsWithType(objectType,
values: value as! [ValueDictionary])
default:
// Convert primitive values, date strings, etc.
newValue = coerceValue(value, toPropertyType:property.type)
}
}
}
func relatedObjectWithClassName(type: Object.Type, values: ValueDictionary) -> Object {
// To-one relations can be instantiated using Object.Type.
var object = type.init()
update(object, values)
return object
}
// To-many relation are generic so there's no way to treat them dynamically.
func listOfRelatedObjectsWithType(type: Object.Type, values: [ValueDictionary]) -> List {
}

Can Swift enums have multiple raw values?

I want to associate two raw values to an enum instance (imagine an enum representing error types, I want Error.Teapot to have an Int type property code with value 418, and a String property set to I'm a teapot.)
Note the difference between raw values and associated values here—I want all Teapot instances to have a code of 418, I don't want a unique associated value for each Teapot instance.
Is there a better way than adding computed properties to the enum that switched on self to look up the appropriate value?
You have a couple options. But neither of them involve raw values. Raw values are just not the right tool for the task.
Option 1 (so-so): Associated Values
I personally highly recommend against there being more than one associated value per enum case. Associated values should be dead obvious (since they don't have arguments/names), and having more than one heavily muddies the water.
That said, it's something the language lets you do. This allows you to have each case defined differently as well, if that was something you needed. Example:
enum ErrorType {
case teapot(String, Int)
case skillet(UInt, [CGFloat])
}
Option 2 (better): Tuples! And computed properties!
Tuples are a great feature of Swift because they give you the power of creating ad-hoc types. That means you can define it in-line. Sweet!
If each of your error types are going to have a code and a description, then you could have a computed info property (hopefully with a better name?). See below:
enum ErrorType {
case teapot
case skillet
var info: (code: Int, description: String) {
switch self {
case .teapot:
return (418, "Hear me shout!")
case .skillet:
return (326, "I'm big and heavy.")
}
}
}
Calling this would be much easier because you could use tasty, tasty dot syntax:
let errorCode = myErrorType.info.code
No, an enum cannot have multiple raw values - it has to be a single value, implementing the Equatable protocol, and be literal-convertible as described in the documentation.
I think the best approach in your case is to use the error code as raw value, and a property backed by a prepopulated static dictionary with the error code as key and the text as value.
I created a way of simulating this (No different than what Marcos Crispino suggested on his answer). Far from a perfect solution but allows us to avoid those nasty switch cases for every different property we want to get.
The trick is to use a struct as the "properties/data" holder and using it as a RawValue in the enum itself.
It has a bit of duplication but it's serving me well so far. Every time you want to add a new enum case, the compiler will remind you to fill in the extra case in the rawValue getter, which should remind you to update the init? which would remind you to create the new static property on the struct.
Gist
Code to the Gist:
enum VehicleType : RawRepresentable {
struct Vehicle : Equatable {
let name: String
let wheels: Int
static func ==(l: Vehicle, r: Vehicle) -> Bool {
return l.name == r.name && l.wheels == r.wheels
}
static var bike: Vehicle {
return Vehicle(name: "Bicycle", wheels: 2)
}
static var car: Vehicle {
return Vehicle(name: "Automobile", wheels: 4)
}
static var bus: Vehicle {
return Vehicle(name: "Autobus", wheels: 8)
}
}
typealias RawValue = Vehicle
case car
case bus
case bike
var rawValue: RawValue {
switch self {
case .car:
return Vehicle.car
case .bike:
return Vehicle.bike
case .bus:
return Vehicle.bus
}
}
init?(rawValue: RawValue) {
switch rawValue {
case Vehicle.bike:
self = .bike
case Vehicle.car:
self = .car
case Vehicle.bus:
self = .bus
default: return nil
}
}
}
VehicleType.bike.rawValue.name
VehicleType.bike.rawValue.wheels
VehicleType.car.rawValue.wheels
VehicleType(rawValue: .bike)?.rawValue.name => "Bicycle"
VehicleType(rawValue: .bike)?.rawValue.wheels => 2
VehicleType(rawValue: .car)?.rawValue.name => "Automobile"
VehicleType(rawValue: .car)?.rawValue.wheels => 4
VehicleType(rawValue: .bus)?.rawValue.name => "Autobus"
VehicleType(rawValue: .bus)?.rawValue.wheels => 8
No, you cannot have multiple raw values associated with an enum.
In your case, you could have the raw value to be equal to the code, and have an associated value with the description. But I think the computed properties approach is the best option here.
One workaround if you wanted to have many static properties for a YourError could be to import a property list; you could set the root object to a dictionary, with your enum raw value as the key for each object, allowing you to easily retrieve static structured data for the object.
This has an example of importing and using a plist: http://www.spritekitlessons.com/parsing-a-property-list-using-swift/
That might be overkill for simply an error description, for which you could just use a hardcoded static function with a switch statement for your enum values, that returns the error string you need. Simply place the static function in the same .swift file as your enum.
For instance,
static func codeForError(error : YourErrorType) -> Int {
switch(error) {
case .Teapot:
return "I'm a Teapot"
case .Teacup:
return "I'm a Teacup"
...
default:
return "Unknown Teaware Error"
}
}
This has the benefit (compared to the .plist solution) of better accomodating localization. However, a .plist could just contain a key used for retrieving the proper localization, instead of a error string, for this purpose.
For beginning, assuming you want to store a code and a message, you can use a struct for RawValue
struct ErrorInfo {
let code: Int
let message: String
}
Next step is to define the enum as being RawRepresentable, and use ErrorInfo as the raw value:
enum MyError: RawRepresentable {
typealias RawValue = ErrorInfo
case teapot
What remains is to map between instances of MyError and ErrorInfo:
static private let mappings: [(ErrorInfo, MyError)] = [
(ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
]
With the above, let's build the full definition of the enum:
enum MyError: RawRepresentable {
static private let mappings: [(ErrorInfo, MyError)] = [
(ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
]
case teapot
init?(rawValue: ErrorInfo) {
guard let match = MyError.mappings.first(where: { $0.0.code == rawValue.code && $0.0.message == rawValue.message}) else {
return nil
}
self = match.1
}
var rawValue: ErrorInfo {
return MyError.mappings.first(where: { $0.1 == self })!.0
}
}
Some notes:
you could use only the error code for matching, however this might result in inconsistent raw values if the messages differ
the amount of boilerplate code required to have raw values of some custom type might not outcome the benefits of using associated values.
Possible work around may to associate custom functions with enum
enum ToolbarType : String{
case Case = "Case", View="View", Information="Information"
static let allValues = [Case, View, Information]
func ordinal() -> Int{
return ToolbarType.allValues.index(of: self)!
}
}
Can be used as
for item in ToolbarType.allValues {
print("\(item.rawValue): \(item.ordinal())")
}
Output
Case: 0
View: 1
Information: 2
Possibly you can have additional functions to associate enum type to different values
This doesn't particularly answer your question, which was asking to find a better way than switching through self to look up the appropriate value but this answer may still be useful for someone looking in the future that needs a simple way to get a string from an enum which is defined as an integer type.
enum Error: UInt {
case Teapot = 418
case Kettle = 419
static func errorMessage(code: UInt) -> String {
guard let error = Error(rawValue: code) else {
return "Unknown Error Code"
}
switch error {
case .Teapot:
return "I'm a teapot!"
case .Kettle:
return "I'm a kettle!"
}
}
}
This way, we can get the errorMessage two ways:
With an integer (eg. that was returned as an error code from a server)
With an enum value (the rawValue we define for the enum)
Option 1:
let option1 = Error.errorMessage(code: 418)
print(option1) //prints "I'm a teapot!"
Option 2:
let option2 = Error.errorMessage(code: Error.Teapot.rawValue)
print(option2) //prints "I'm a teapot!"
In modern versions of Swift it's possible to get the string value of an enum case label, even without that enum being declared with a : String rawValue.
How to get the name of enumeration value in Swift?
So there is no longer a need to define and maintain a convenience function that switches on each case to return a string literal. In addition, this works automatically for any enum, even if no raw-value type is specified.
This, at least, allows you to have "multiple raw values" by having both a real : Int rawValue as well as the string used as the case label.
I think it just tricky, and I have create my own idea like below:
enum Gender:NSNumber
{
case male = 1
case female = 0
init?(strValue: String?) {
switch strValue {
case Message.male.value:
self = .male
case Message.female.value:
self = .female
default: return nil
}
}
var strValue: String {
switch self {
case .male:
return Message.male.value
case .female:
return Message.female.value
}
}
}
First of all, enums should only have one raw value. However if you want to have something that can use multiple raw values... there is a way to 'hack' this, but you have to make it codable and hashable yourself, implement custom init's etc.
enum MyCustomEnum: Codable, Hashable {
// duplicate every case with associated value of Codable.Type
case myFirstCase, _myFirstCase(Codable.Type)
case mySecondCase, _mySecondCase(Codable.Type)
case myThirdCase, _myThirdCase(Codable.Type)
case unknown(Any), _unknown(Codable.Type, Any) // handles unknown values
// define an allCases value to determine the only values your app 'sees'.
static var allCases: [Self] {
return [
.myFirstCase,
.mySecondCase,
.myThirdCase
// unknown(String) // you can add unknown as well, but this is too mask any unknown values.
]
}
static func == (lhs: MyCustomEnum, rhs: MyCustomEnum) -> Bool {
return lhs.stringValue == rhs.stringValue // can be either one of your custom raw values.
}
// add this per raw value. In this case one for Int and one for String
init(rawValue: Int) {
guard let value = Self.allCases.first(where:{ $0.intValue == rawValue }) else {
self = ._unknown(Int.self, rawValue)
return
}
switch value {
case .myFirstCase: self = ._myFirstCase(Int.self)
case .mySecondCase: self = ._mySecondCase(Int.self)
case .myThirdCase: self = ._myThirdCase(Int.self)
default: self = ._unknown(Int.self, rawValue)
}
}
init(rawValue: String) {
guard let value = Self.allCases.first(where:{ $0.stringValue == rawValue }) else {
self = ._unknown(String.self, rawValue)
return
}
switch value {
case .myFirstCase: self = ._myFirstCase(String.self)
case .mySecondCase: self = ._mySecondCase(String.self)
case .myThirdCase: self = ._myThirdCase(String.self)
default: self = ._unknown(Int.self, rawValue)
}
}
// add this per raw value. In this case one for Int and one for String
var intValue: Int {
switch self {
case .myFirstCase, ._myFirstCase(_): return 1
case .mySecondCase, ._mySecondCase(_): return 2
case .myThirdCase, ._myThirdCase(_): return 3
case .unknown(let value), ._unknown(_, let value): return value as? Int ?? -1 // you can also choose to let intValue return optional Int.
}
}
var stringValue: String {
switch self {
case .myFirstCase, ._myFirstCase(_): return "my first case"
case .mySecondCase, ._mySecondCase(_): return "my second case"
case .myThirdCase, ._myThirdCase(_): return "my third case"
case .unknown(let value), ._unknown(_, let value): return value as? String ?? "not a String" // you can also choose to let stringValue return optional String.
}
}
// determine the codable type using Mirror
private func getCodableType() -> Codable.Type? {
let mirrorOfModuleType = Mirror.init(reflecting: self)
guard let childOfModuleType = mirrorOfModuleType.children.first else { // no children, means no associated values.
return nil
}
let value = childOfModuleType.value // can be either Codable.Type, String or (Codable.Type & String)
if let rawValue = value as? Codable.Type {
return rawValue
} else {
guard let rawValue = value as? (Codable.Type, String) else {
// unknown(String), we don't know the rawValue as given, but try in this part of the code to guess what type fits best.
if self.stringValue != "\(self.intValue)" { // e.g. "1" might match 1 but "1.0" and 1 don't match
return String.self
} else {
return Int.self // return either a default value, or nil. It's your choice.
}
}
return rawValue.0
}
}
// confine to hashable using getCodableType
func hash(into hasher: inout Hasher) {
if self.getCodableType() is String.Type {
hasher.combine(self.stringValue)
} else { // if you don't call hasher.combine at all, you can expect strange issues. If you do not know the type, choose one that is most common.
hasher.combine(self.intValue)
}
}
// confine to Decodable
init(from decoder: Decoder) throws {
if let rawValue = try? Int.init(from: decoder) {
self.init(rawValue: rawValue)
} else if let rawValue = try? String.init(from: decoder) {
self.init(rawValue: rawValue)
} else {
throw DecodingError.valueNotFound(Self.self, DecodingError.Context(codingPath: [], debugDescription: "no matching value was found"))
}
}
// confine to Encodable using getCodableType
func encode(to encoder: Encoder) throws {
let rawValue = self.getCodableType()
if rawValue is String.Type {
try self.stringValue.encode(to: encoder)
} else if rawValue is Int.Type {
try self.intValue.encode(to: encoder)
} else {
// getCodableType returns nil if it does not know what value it is. (e.g. myFirstCase without associated value) If you want to support this as well, you can encode using one of your rawValues to the encoder.
throw EncodingError.invalidValue(Self.self, EncodingError.Context.init(codingPath: [], debugDescription: "this enum does not have a correct value", underlyingError: nil))
}
}
}
this code is scalable to any number of raw value as long as they are Codable