Is there a Swift type for a string-based enum? - swift

I have a function that takes a String tag argument:
func findFooByTag(_ tag: String) -> Foo
Now I would like to make the code shorter and safer by introducing an enum for the valid tag values:
enum Tags: String {
case one
case two
case three
}
But I still have to call the function with a String:
let foo = findFooByTag(Tags.one.rawValue)
Is there a way to say “findFooByTag takes any string-based enum”? I have found this:
func findFooByTag<T>(_ tag: T) -> Foo where T: RawRepresentable, T.RawValue == String
But that’s quite a mouthful. Is it at least possible to sweep that under the rug with a type alias somehow?

What you have found looks awesome, but still I would suggest something like the following:
protocol Taggable {
var raw: String { get }
}
extension String: Taggable {
var raw: String { return self }
}
enum Tag: String, Taggable {
var raw: String {
return self.rawValue
}
case one = "aa"
case two = "bb"
case three = "cc"
}
func findByTag(_ tag: Taggable) {
if tag.raw == "aa" { ... }
// do something...
}
findByTag(Tag.one) // works
findByTag("abc") // also works

As there is nothing in common between enum's having a String RawValue, there is no common type for these or no protocol to which all would conform.
However, Swift 4 introduces type constraints on associated types using where clauses as described in SE-0142. Using this new capability, you can define a protocol with an associated type the type constraints describing an enum with a String rawValue, then you only need to make your Tags enum conform to this protocol and you won't need the type constraint in your function definition anymore.
class Foo {}
protocol StringEnum {
associatedtype EnumType: RawRepresentable = Self where EnumType.RawValue == String
static func findFooByTag<EnumType>(_ tag: EnumType) -> Foo
}
extension StringEnum where EnumType == Self {
static func findFooByTag<EnumType>(_ tag: EnumType) -> Foo {
return Foo()
}
}
enum Tags: String, StringEnum {
case one
case two
case three
}
let foo = Tags.findFooByTag(Tags.one)
This is implementation of course could be improved, but this is just an example showing how you can use a where clause to implement the type constraint using a protocol and its associatedType.
Due to the default implementation fo the findFooByTag func in the protocol extension, you don't need to implement the function for all of your custom enum types having a String rawValue, you only need to declare them as conforming to the StringEnum protocol.
If you don't have Xcode9 installed, you can play around with this code in the IBM Swift Sandbox using this link.

Maybe you can try do that with CustomStringConvertible?
enum Tags: String, CustomStringConvertible {
case one
case two
case three
var description: String {
return self.rawValue
}
}
func findFooByTag<T>(_ tag: T) -> Foo where T: CustomStringConvertible
looks more better
or just
func findFooByTag<T>(_ tag: CustomStringConvertible) -> Foo

For this purpose you can use any kind of wrapper object. For example:
enum TypeNotSpecifiedTag {
case one
}
enum StringTag: String {
case one
}
enum IntTag: Int {
case one = 1
}
enum Wrapper<T>: RawRepresentable {
typealias RawValue = T
case value(T)
init?(rawValue: RawValue) {
self = .value(rawValue)
}
var rawValue: RawValue {
switch self {
case let .value(item):
return item
}
}
}
print(Wrapper.value(TypeNotSpecifiedTag.one).rawValue) // "one"
print(Wrapper.value(StringTag.one.rawValue).rawValue) // "one"
print(Wrapper.value(IntTag.one.rawValue).rawValue) // 1
Notice, that according to documentation about RawValue, you doesn't always need to specify RawValue, that's why first example also compile.

Related

How can I create a function in Swift that returns a Type which conforms to a protocol?

How can I create a function in Swift that returns a Type which conforms to a protocol?
Here is what I'm trying right now, but it obviously won't compile like this.
struct RoutingAction {
enum RoutingActionType{
case unknown(info: String)
case requestJoinGame(gameName: String)
case requestCreateGame(gameName: String)
case responseJoinGame
case responseCreateGame
}
// Any.Type is the type I want to return, but I want to specify that it will conform to MyProtocol
func targetType() throws -> Any.Type:MyProtocol {
switch self.actionType {
case .responseCreateGame:
return ResponseCreateGame.self
case .responseJoinGame:
return ResponseJoinGame.self
default:
throw RoutingError.unhandledRoutingAction(routingActionName:String(describing: self))
}
}
}
I would personally prefer returning an instance instead of a type but you can do it that way too. Here's one way to achieve it:
protocol MyProtocol:class
{
init()
}
class ResponseCreateGame:MyProtocol
{
required init() {}
}
class ResponseJoinGame:MyProtocol
{
required init() {}
}
enum RoutingActionType
{
case unknown(info: String),
requestJoinGame(gameName: String),
requestCreateGame(gameName: String),
responseJoinGame,
responseCreateGame
// Any.Type is the type I want to return, but I want to specify that it will conform to MyProtocol
var targetType : MyProtocol.Type
{
switch self
{
case .responseCreateGame:
return ResponseCreateGame.self as MyProtocol.Type
case .responseJoinGame:
return ResponseJoinGame.self as MyProtocol.Type
default:
return ResponseJoinGame.self as MyProtocol.Type
}
}
}
let join = RoutingActionType.responseJoinGame
let objectType = join.targetType
let object = objectType.init()
Note that your protocol will need to impose a required init() to allow creation of instances using the returned type.
Note2: I changed the structure a little to make my test easier but i'm sure you'll be able to adapt this sample to your needs.
Why do you not want to use simple:
func targetType() throws -> MyProtocol
?
EDIT:
I think you can't. Because if you return Type actually you return an instance of class Class and it can't conform your protocol. This runtime's feature was inherited from objective-c. You can see SwiftObject class.

if-let Any to RawRepresentable<String>

Let's assume this:
enum MyEnum: String { case value }
let possibleEnum: Any = MyEnum.value
if let str = stringFromPossibleEnum(possibleEnum: possibleEnum)
What's my best bet of implementing stringFromPossibleEnum without knowing enum type name?
func stringFromPossibleEnum(possibleEnum: Any) -> String? {
// how should this be implemented without knowing enum type name?
}
UPD: ok, it's getting better, with this I can tell if possibleEnum is an enum:
if Mirror(reflecting: possibleEnum).displayStyle == .enum { print("yes!") }
But how to tell if that's a String-based enum?
UPD: this tweet suggests that you can get rawValue as Any from Enum. You can probably then check if that rawValue is String. But how to get rawValue from Mirror?
Ok, so this is basically not doable currently out of the box, as you can't as?-cast to RawRepresentable, and Mirror does not provide rawValue for enums.
I'd say the best bet is to make own protocol, provide default implementation for String-based RawRepresentable and conform all enums manually like so:
Assuming these are the enums:
enum E1: String { case one }
enum E2: String { case two }
enum E3: String { case three }
StringRawRepresentable protocol and default implementation:
protocol StringRawRepresentable {
var stringRawValue: String { get }
}
extension StringRawRepresentable
where Self: RawRepresentable, Self.RawValue == String {
var stringRawValue: String { return rawValue }
}
Conform all needed existing enums to the protocol:
extension E1: StringRawRepresentable {}
extension E2: StringRawRepresentable {}
extension E3: StringRawRepresentable {}
And now we can cast to StringRawRepresentable:
func stringFromPossibleEnum(possibleEnum: Any) -> String? {
if let e = possibleEnum as? StringRawRepresentable { return e.stringRawValue }
return nil
}
stringFromPossibleEnum(possibleEnum: E2.two as Any)
Not sure what you're really trying to achieve here, but here it is:
enum MyEnum: String {
case A
case B
case C
}
func stringFromEnum<T: RawRepresentable>(_ value: T) -> String
where T.RawValue == String {
return value.rawValue
}
print(stringFromEnum(MyEnum.A))
print(stringFromEnum(MyEnum.B))
print(stringFromEnum(MyEnum.C))

Swift "Enum" base object

If I want to add an extension to every object I can do the following:
extension AnyObject
{
func myFunc() { ... }
}
Is there something similar where I can add a function to every Enum? In other words, what is the base "class" for every enum?
First of all, note that you cannot do an extension to AnyObject as above, as AnyObject is a protected protocol (to which all classes implicitly conform) rather than a type. See e.g.
Is there a way to add an extension to AnyObject?
Now, you could, however, extend any specific type as you show above, e.g., extension Int { ... }. However enum is not a type; rather a "container" of ordered raw representable values. So a more valid comparison could be "If I want to add an extension to every class, by extension class ...", which is, naturally, trickier.
Now, all enumerations with a rawValue and an initializer by rawValue conforms to protocol RawRepresentable, so you could extend this protocol for specific types of raw values:
extension RawRepresentable where RawValue == Int {
var sqrt: Double {
return pow(Double(rawValue),(1/2))
}
}
And this extension property would be available to all enumerations that explicitly use the same raw value type, in this case, Int:
enum MyIntegerLiteralEnum : Int {
case One = 1
case Two
case Three
case Four
}
print(MyIntegerLiteralEnum.One.sqrt)
print(MyIntegerLiteralEnum.Two.sqrt)
print(MyIntegerLiteralEnum.Four.sqrt)
/* 1.0
1.4142135623731
2.0 */
As a disclaimer, note that this extension will be made available to all types that conforms to RawRepresentable with a rawValue of type Int, not only enum types. E.g.:
struct Foo : RawRepresentable {
typealias RawValue = Int
var bar : Int
var rawValue: RawValue {
get {
return bar
}
}
init(rawValue bar: Int) {
self.bar = bar
}
}
var a = Foo(rawValue: 16)
print(a.sqrt) // 4.0

Swift 2: How to refer to a type of an element in default implementation of a function in a protocol

I need to get some objects from REST API and map them to local objects using ObjectMapper.
These objects contain a number of enumerations. All of them are coming as integers and I want to map them to locally described enums.
To do that I have to describe standard transform function that is used by ObjectMapper.
enum Types: Int {
case Uno = 1
case Dos = 2
case Tres = 3
static var transform = TransformOf<Types,Int>(
fromJSON: {
$0 != nil
? Types(rawValue:$0!)
: nil
},
toJSON: { $0?.rawValue})
}
The problem is that I have a number of these enumerations and the function is totally same in all of them except that first argument in TransformOf<..> list is specific for each enum.
What I want to do is to create a common protocol with default implementation of that function, something like
protocol Transformable {
var transform: TransformOf<self.Type,Int> {
get {
return TransformOf<self.Type,Int>(
fromJSON: {
$0 != nil
? Types(rawValue:$0!)
: nil
},
toJSON: { $0?.rawValue})
}
}
}
...and then to apply the protocol with the implementation to all of the enumerations I have.
Obviously reference of self.Type is not working there and I just can't get how to generally refer to type of specific instance that will finally use the function? Probably I'm thinking wrong way of solving that problem.
I think what you're missing is the Self identifier. When implementing Generics, the Self keyword acts as a placeholder for the type that implements your protocol. (For more information)
In other words:
protocol Transformable {
var rawValue: Int { get }
init?(rawValue: Int)
func toJSON() -> Int
static func fromJSON(rawValue: Int) -> Self?
}
Each enum that conforms to the protocol Transformable will then have a static method that returns its own type.
Secondly, since this is Swift 2, you can implement a protocol extension:
extension Transformable {
func toJSON() -> Int {
return self.rawValue
}
static func fromJSON(rawValue: Int) -> Self? {
return Self(rawValue: rawValue)
}
}
Now all enums that conform to the protocol will convert themselves to and from Int:
enum Types: Int, Transformable {
case Uno = 1
case Dos = 2
case Tres = 3
//No extra implementation
}
enum OtherTypes: Int, Transformable {
case Cuatro = 4
case Cinco = 5
case Seis = 6
//No extra implementation
}
print(Types.fromJSON(1)!) //prints Uno
print(OtherTypes.fromJSON(4)!) //prints Cuatro
print(Types.fromJSON(4)!) /* throws an error, because the Types enum
does not recognise 4 as a raw value */

Adding a case to an existing enum with a protocol

I want to create a protocol that enforces a certain case on all enums conforming to this protocol.
For example, if I have a enum like this:
enum Foo{
case bar(baz: String)
case baz(bar: String)
}
I want to extend it with a protocol that adds another case:
case Fuzz(Int)
Is this possible?
Design
The work around is to use a struct with static variables.
Note: This is what is done in Swift 3 for Notification.Name
Below is an implementation on Swift 3
Struct:
struct Car : RawRepresentable, Equatable, Hashable, Comparable {
typealias RawValue = String
var rawValue: String
static let Red = Car(rawValue: "Red")
static let Blue = Car(rawValue: "Blue")
//MARK: Hashable
var hashValue: Int {
return rawValue.hashValue
}
//MARK: Comparable
public static func <(lhs: Car, rhs: Car) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
Protocol
protocol CoolCar {
}
extension CoolCar {
static var Yellow : Car {
return Car(rawValue: "Yellow")
}
}
extension Car : CoolCar {
}
Invoking
let c1 = Car.Red
switch c1 {
case Car.Red:
print("Car is red")
case Car.Blue:
print("Car is blue")
case Car.Yellow:
print("Car is yellow")
default:
print("Car is some other color")
}
if c1 == Car.Red {
print("Equal")
}
if Car.Red > Car.Blue {
print("Red is greater than Blue")
}
Note:
Please note this approach is not a substitute for enum, use this only when the values are not known at compile time.
no, since you can't declare a case outside of an enum.
An extension can add a nested enum, like so:
enum Plants {
enum Fruit {
case banana
}
}
extension Plants {
enum Vegetables {
case potato
}
}
Here are a couple additional takes that may help somebody out there:
Using your example:
enum Foo {
case bar(baz: String)
case baz(bar: String)
}
You can consider to "nest" it in a case of your own enum:
enum FooExtended {
case foo(Foo) // <-- Here will live your instances of `Foo`
case fuzz(Int)
}
With this solution, it becomes more laborious to access the "hidden" cases associated type. But this simplification could actually be beneficial in certain applications.
Another alternative passes by just recreate and extend it while having a way to convert Foo into the extended enum FooExtended (eg. with a custom init):
enum FooExtended {
case bar(baz: String)
case baz(bar: String)
case fuzz(Int)
init(withFoo foo: Foo) {
switch foo {
case .bar(let baz):
self = .bar(baz: baz)
case .baz(let bar):
self = .baz(bar: bar)
}
}
}
There may be many places where one, the other, or both of these solutions make absolutely no sense, but I'm pretty sure they may be handy to somebody out there (even if only as an exercise).