Avoid the usage of a switch statement when parsing - swift

I have been struggling to develop a code to avoid a big switch statement.
Basically I am given a set of tuples as input and now I would like to instantiate the appropriate class depending on the type specified in the tuple (first element of the tuple - e.g. ("Apple", ...)).
Currently I solved my problem by using a switch statement but this is a very bad idea if the number of classes increase in the future. Is there some elegant way to circumvent this issue?
Thank you!
class Apple {
var color = ""
}
class Banana {
var isTasty = false
}
let input = [("Apple", "green"),("Banana", "TRUE")]
for (type, value) in input {
switch type {
case "Apple":
Apple()
case "Banana":
Banana()
default:
print(value)
}
}

Check this out,
// Parent Class to group them
class Fruit {
var baseValue: Any
required init(_ this: Any) {
baseValue = this
(self as? Apple)?.color = this as! String
(self as? Banana)?.isTasty = this as! Bool
// You can add more subclasses here
}
}
class Apple: Fruit, Equatable {
var color = ""
func hash(into hasher: inout Hasher) {
hasher.combine(color)
}
static func == (lhs: Apple, rhs: Apple) -> Bool {
return lhs.color == rhs.color
}
}
class Banana: Fruit, Equatable {
var isTasty = false
func hash(into hasher: inout Hasher) {
hasher.combine(isTasty)
}
static func == (lhs: Banana, rhs: Banana) -> Bool {
return lhs.isTasty == rhs.isTasty
}
}
It's a little annoying that they have to conform to Fruit, Hashable, and Equatable. But it let's you do this, which answers your question:
let input: [(Fruit.Type,Any)] = [(Apple.self, "green"),(Banana.self, true)]
for (type, value) in input {
// Now you don't need that switch statement
let foo = type.init(value)
// Now you can use `as?` to find out what it is
print((foo as? Banana)?.isTasty)
// See, it prints `nil` first, then `Optional(true)` next.
// Which is how we want it to run
}
However, I think we can do even better.
enum Fruit {
case apple(color: String)
case banana(isTasty: Bool)
}
let input: [Fruit] = [.apple(color: "red"), .banana(isTasty: true)]
Much better.

Related

Swift enum protocol conformance

I want to create a Swift protocol that my different enums can conform to, so I can use the same 'type' while utilizing the enum's rawValue. Basically, the protocol should look like this:
protocol SidebarCustomFilter {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
An example enum that conforms to this:
enum SidebarFilterLogs : String, CaseIterable, SidebarCustomFilter{
case filterAll = "All"
case filterThisWeek = "This Week"
var image: UIImage {
return UIImage(systemName: "tray.full.fill")!
}
var filterPredicate: NSPredicate {
return NSPredicate.init(format: "TRUEPREDICATE")
}
}
Now I want to use this enum inside a struct:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter?
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
So far so good. However, if I try to use the 'rawValue' for the enum (of the protocol type I've described above), it gives me an error
sidebarItem.sidebarCustomFilter?.rawValue // Value of type 'SidebarCustomFilter' has no member 'rawValue'
That makes sense, since the SidebarCustomFilter is only a protocol. However, if I try to make it inherit from RawRepresentable (which should allow it to work with rawValue),
protocol SidebarCustomFilter: RawRepresentable {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
I get a different error:
var sidebarCustomFilter: SidebarCustomFilter? // Protocol 'SidebarCustomFilter' can only be used as a generic constraint because it has Self or associated type requirements
I believer this has something to do with RawRepresentable using an associatedtype RawValue, but I'm not sure how to resolve it.
So how do I get my protocol to work such that it enforces that only other enums are conforming to it (and hence it's valid to use 'rawValue' against it)?
Found a solution:
protocol SidebarCustomFilter {
var name: String { get }
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
extension SidebarCustomFilter where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
Now I can use sidebarItem.sidebarCustomFilter?.name instead of directly asking for the rawValue, and it works!
EDIT: adding code to show how it's stored:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter? // storing as generic type
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
and how it's used:
let titleString = sidebarItem.sidebarCustomFilter?.name ?? ""

Swift set equatable sometimes true sometimes false

I have a struct conforming to Hashable. This model is put in a Set. Randomly when I check if the set contains the model it returns true/false. Why is this?
enum Feature: String {
case a
case b
}
struct FeatureState: Hashable {
let feature: Feature
let isEnabled: Bool
}
extension FeatureState: Equatable {
static func == (lhs: FeatureState, rhs: FeatureState) -> Bool {
lhs.feature == rhs.feature
}
}
let fs1 = FeatureState(feature: .a, isEnabled: false)
let fs2 = FeatureState(feature: .a, isEnabled: true)
featureStates.insert(fs1)
print(featureStates.contains(fs2)) // sometimes true, sometimes false
Set.contains uses hashes to check whether an element is already part of the Set or not and only uses the == operator if the hash values of two elements are the same. Because of this, you need to provide your own hash(into:) implementation to make the hash value only dependant on feature, but not isEnabled.
struct FeatureState {
let feature: Feature
let isEnabled: Bool
}
extension FeatureState: Hashable {
static func == (lhs: FeatureState, rhs: FeatureState) -> Bool {
lhs.feature == rhs.feature
}
func hash(into hasher: inout Hasher) {
hasher.combine(feature)
}
}

Array of protocol type

I have checked all answers about this problem on stackoverflow, but still can not figure out how to fix this.
My model looks like this
protocol Commandable: Equatable {
var condition: Condition? {get set}
func execute() -> SKAction
}
And 3 structs which implement this protocol
struct MoveCommand: Commandable {
var movingVector: CGVector!
//MARK: - Commandable
var condition: Condition?
func execute() -> SKAction {
...
}
}
extension MoveCommand {
// MARK:- Equatable
static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
return lhs.movingVector == rhs.movingVector && lhs.condition == rhs.condition
}
}
struct RotateCommand: Commandable {
var side: RotationSide!
// MARK: - Commandable
var condition: Condition?
func execute() -> SKAction {
...
}
}
extension RotateCommand {
// MARK: - Equatable
static func ==(lhs: RotateCommand, rhs: RotateCommand) -> Bool {
return lhs.side == rhs.side && lhs.condition == rhs.condition
}
}
The problems start when I am trying to create third structure which has array of [Commandable]:
struct FunctionCommand: Commandable {
var commands = [Commandable]()
The compiler output: Protocol 'Commandable' can only be used as a generic constraint because it has Self or associated type requirements. Then i rewrote my struct in this way:
struct FunctionCommand<T : Equatable>: Commandable {
var commands = [T]()
I resolve this problem but new problem has appeared. Now i can't create FunctionCommand with instances of Rotate and Move command, only with instances of one of them :( :
let f = FunctionCommand(commands: [MoveCommand(movingVector: .zero, condition: nil),
RotateCommand(side: .left, condition: nil)], condition: nil)
Any Help would be appreciated.
Update: That article helped me to figure out - https://krakendev.io/blog/generic-protocols-and-their-shortcomings
What you need to do is to use type erasure, much like AnyHashable does in the Swift Standard Library.
You can't do:
var a: [Hashable] = [5, "Yo"]
// error: protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements
What you have to do is to use the type-erased type AnyHashable:
var a: [AnyHashable] = [AnyHashable(5), AnyHashable("Yo")]
a[0].hashValue // => shows 5 in a playground
So your solution would be to first split the protocol in smaller parts and promote Equatable to Hashable (to reuse AnyHashable)
protocol Conditionable {
var condition: Condition? { get set }
}
protocol Executable {
func execute() -> SKAction
}
protocol Commandable: Hashable, Executable, Conditionable {}
Then create an AnyCommandable struct, like this:
struct AnyCommandable: Commandable, Equatable {
var exeBase: Executable
var condBase: Conditionable
var eqBase: AnyHashable
init<T: Commandable>(_ commandable: T) where T : Equatable {
self.condBase = commandable
self.exeBase = commandable
self.eqBase = AnyHashable(commandable)
}
var condition: Condition? {
get {
return condBase.condition
}
set {
condBase.condition = condition
}
}
var hashValue: Int {
return eqBase.hashValue
}
func execute() -> SKAction {
return exeBase.execute()
}
public static func ==(lhs: AnyCommandable, rhs: AnyCommandable) -> Bool {
return lhs.eqBase == rhs.eqBase
}
}
And then you can use it like this:
var a = FunctionCommand()
a.commands = [AnyCommandable(MoveCommand()), AnyCommandable(FunctionCommand())]
And you can easily access properties of commands, because AnyCommandable implements Commandable
a.commands[0].condition
You need to remember to now add Hashable and Equatable to all your commands.
I used those implementations for testing:
struct MoveCommand: Commandable {
var movingVector: CGVector!
var condition: Condition?
func execute() -> SKAction {
return SKAction()
}
var hashValue: Int {
return Int(movingVector.dx) * Int(movingVector.dy)
}
public static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
return lhs.movingVector == rhs.movingVector
}
}
struct FunctionCommand: Commandable {
var commands = [AnyCommandable]()
var condition: Condition?
func execute() -> SKAction {
return SKAction.group(commands.map { $0.execute() })
}
var hashValue: Int {
return commands.count
}
public static func ==(lhs: FunctionCommand, rhs: FunctionCommand) -> Bool {
return lhs.commands == rhs.commands
}
}
I think it can be easily done by introduction of your own CustomEquatable protocol.
protocol Commandable: CustomEquatable {
var condition: String {get}
}
protocol CustomEquatable {
func isEqual(to: CustomEquatable) -> Bool
}
Then, you objects have to conform to this protocol and additionally it should conform Equitable as well.
struct MoveCommand: Commandable, Equatable {
let movingVector: CGRect
let condition: String
func isEqual(to: CustomEquatable) -> Bool {
guard let rhs = to as? MoveCommand else { return false }
return movingVector == rhs.movingVector && condition == rhs.condition
}
}
struct RotateCommand: Commandable, Equatable {
let side: CGFloat
let condition: String
func isEqual(to: CustomEquatable) -> Bool {
guard let rhs = to as? RotateCommand else { return false }
return side == rhs.side && condition == rhs.condition
}
}
All you need to do now is connect your CustomEquatable protocol to Swift Equatable through generic extension:
extension Equatable where Self: CustomEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.isEqual(to: rhs)
}
}
It's not a perfect solution, but now, you can store your objects in a array of protocol objects and use == operator with your objects as well. For example(I simplified objects a little bit):
let move = MoveCommand(movingVector: .zero, condition: "some")
let rotate = RotateCommand(side: 0, condition: "some")
var array = [Commandable]()
array.append(move)
array.append(rotate)
let equal = (move == MoveCommand(movingVector: .zero, condition: "some"))
let unequal = (move == MoveCommand(movingVector: .zero, condition: "other"))
let unequal = (move == rotate) // can't do this, compare different types
PS. Using var on struct is not a good practice, especially for performance reasons.
I believe the problem here is that the equatable protocol has self requirements. So you can solve you problem by removing equatable protocol from your Commandable protocol and make your your structs equatable instead. This will of course limit your protocol but maybe it is a trade-off that is reasonable?

Iterating through an Enum in Swift 3.0

I have a simple enum that I would like to iterate over. For this purpose, I've adopted Sequence and IteratorProtocol as shown in the code below. BTW, this can be copy/pasted to a Playground in Xcode 8.
import UIKit
enum Sections: Int {
case Section0 = 0
case Section1
case Section2
}
extension Sections : Sequence {
func makeIterator() -> SectionsGenerator {
return SectionsGenerator()
}
struct SectionsGenerator: IteratorProtocol {
var currentSection = 0
mutating func next() -> Sections? {
guard let item = Sections(rawValue:currentSection) else {
return nil
}
currentSection += 1
return item
}
}
}
for section in Sections {
print(section)
}
But the for-in loop generates the error message "Type 'Sections.Type' does not conform to protocol 'Sequence'".
The protocol conformance is in my extension; so, what is wrong with this code?
I know there are other ways of doing this but I'd like to understand what's wrong with this approach.
Thanks.
Note that Martin’s solution can be refactored as a protocol:
import Foundation
protocol EnumSequence
{
associatedtype T: RawRepresentable where T.RawValue == Int
static func all() -> AnySequence<T>
}
extension EnumSequence
{
static func all() -> AnySequence<T> {
return AnySequence { return EnumGenerator() }
}
}
private struct EnumGenerator<T: RawRepresentable>: IteratorProtocol where T.RawValue == Int {
var index = 0
mutating func next() -> T? {
guard let item = T(rawValue: index) else {
return nil
}
index += 1
return item
}
}
Then, given an enum
enum Fruits: Int {
case apple, orange, pear
}
you slap the protocol and a typealias:
enum Fruits: Int, EnumSequence {
typealias T = Fruits
case apple, orange, pear
}
Fruits.all().forEach({ print($0) }) // apple orange pear
Update: As of Swift 4.2, you can simply add protocol conformance
to CaseIterable, see How to enumerate an enum with String type?.
You can iterate over a value of a type which conforms to the Sequence
protocol. Therefore
for section in Sections.Section0 {
print(section)
}
would compile and give the expected result. But of course that is not
really what you want because the choice of the value is arbitrary and the
value itself not needed in the sequence.
As far as I know, there is no way to iterate over a type itself, so that
for section in Sections {
print(section)
}
compiles. That would require that the "metatype" Sections.Type conforms
to Sequence. Perhaps someone proves me wrong.
What you can do is to define a type method which returns a sequence:
extension Sections {
static func all() -> AnySequence<Sections> {
return AnySequence {
return SectionsGenerator()
}
}
struct SectionsGenerator: IteratorProtocol {
var currentSection = 0
mutating func next() -> Sections? {
guard let item = Sections(rawValue:currentSection) else {
return nil
}
currentSection += 1
return item
}
}
}
for section in Sections.all() {
print(section)
}
Simply add to enum:
static var allTypes: [Sections] = [.Section0, .Section1, .Section2]
And than you can:
Sections.allTypes.forEach { (section) in
print("\(section)")
}
This looks so much simpler:
public protocol EnumSequence {
init?(rawValue: Int)
}
public extension EnumSequence {
public static var items: [Self] {
var caseIndex: Int = 0
let interator: AnyIterator<Self> = AnyIterator {
let result = Self(rawValue: caseIndex)
caseIndex += 1
return result
}
return Array(interator)
}
}
Iterated upon the solutions above, see below a protocol that can be implemented by enumerations to add the allValues sequence but also to allow the possibility to convert to and from string value.
Very convenient for String-like enumerations which need to support objective c (only int enumerations are allowed there).
public protocol ObjcEnumeration: LosslessStringConvertible, RawRepresentable where RawValue == Int {
static var allValues: AnySequence<Self> { get }
}
public extension ObjcEnumeration {
public static var allValues: AnySequence<Self> {
return AnySequence {
return IntegerEnumIterator()
}
}
public init?(_ description: String) {
guard let enumValue = Self.allValues.first(where: { $0.description == description }) else {
return nil
}
self.init(rawValue: enumValue.rawValue)
}
public var description: String {
return String(describing: self)
}
}
fileprivate struct IntegerEnumIterator<T: RawRepresentable>: IteratorProtocol where T.RawValue == Int {
private var index = 0
mutating func next() -> T? {
defer {
index += 1
}
return T(rawValue: index)
}
}
For a concrete example:
#objc
enum Fruit: Int, ObjcEnumeration {
case apple, orange, pear
}
Now you can do:
for fruit in Fruit.allValues {
//Prints: "apple", "orange", "pear"
print("Fruit: \(fruit.description)")
if let otherFruit = Fruit(fruit.description), fruit == otherFruit {
print("Fruit could be constructed successfully from its description!")
}
}
If your enum is an Int based one, you can do an effective but slightly dirty trick like this.
enum MyEnum: Int {
case One
case Two
}
extension MyEnum {
func static allCases() -> [MyEnum] {
var allCases = [MyEnum]()
for i in 0..<10000 {
if let type = MyEnum(rawValue: i) {
allCases.append(type)
} else {
break
}
}
return allCases
}
}
Then loop over MyEnum.allCases()..

Swift: Hashable struct with dictionary property

I have a struct in Swift that looks like this:
internal struct MapKey {
internal let id: String
internal let values: [String:String]
}
extension MapKey: Equatable {}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
I now have the need to use MapKey as the key in a Swift dictionary, which requires MapKey to conform to the Hashable protocol.
What would a correct implementation of Hashable be for a struct like this one?
extension MapKey: Hashable {
var hashValue: Int {
return ??? // values does not have a hash function/property.
}
}
I've been doing some research but failed to identify what the proper way to hash a dictionary is, as I need to be able to generate a hash value for values property itself. Any help is much appreciated.
I think you need to review your data model if you have to use a whole struct as a dictionary key. Anyhow, here's one way to do it:
internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]
var hashValue: Int {
get {
var hashString = self.id + ";"
for key in values.keys.sort() {
hashString += key + ";" + values[key]!
}
return hashString.hashValue
}
}
}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
This assumes that you don't have semicolon (;) in id or in the keys and values of values. Hasable implies Equatable so you don't need to declare it conforming to Equatable again.
Since both id and values are immutable both are ok to use as basis for equals and hashValue.
However - if MapKey.id (which the name somewhat implies) uniquely identifies the MapKey (at least within the context of one dictionary)
then it is both easier and more performant to just use the MakKey.id as basis for == operator as well as hashValue
internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]
var hashValue: Int {
get { return self.id.hashValue}
}
}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id
}
foundation data types are Hashable in Swift 4.2, you only need to let your MapKey struct to conform Hashable protocol:
struct MapKey: Hashable {
let id: String
let values: [String: String]
}
in case you want to use a class, you need conform hash(:) func like this:
class MapKey: Hashable {
static func == (lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
let id: String = ""
let values: [String: String] = [:]
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(values)
}
}