Adrian
Adrian

Reputation: 16715

Refactor struct as an enum in Swift

Today someone commented on this code and suggested it would be better in an enum:

typealias PolicyType = (filename: String, text: String)

struct Policy {
  static let first = PolicyType(filename: "firstFile.txt", text: "text in first file")
  static let second = PolicyType(filename: "secondFile.txt", text: "text in second file")
  static let third = PolicyType(filename: "thirdFile.txt", text: "text in third file")
}

let thirdPolicyText = Policy.third.text

Is there a more memory efficient, maintainable way to do this with an enum? My primary objective is maintainability.

Below is what I've come up with:

enum Policy: RawRepresentable {
  case one
  case two
  case three

  var rawValue: (filename: String, text: String) {
    switch self {
    case .one:
      return ("1", "policy 1 text")
    case .two:
      return ("2", "policy 2 text")
    case .three:
      return ("3", "policy 3 text")
    }
  }

  init?(rawValue: (filename: String, text: String)) {
    switch rawValue {
    case ("1", "policy 1 text"):
      self = .one
    case ("2", "policy 2 text"):
      self = .two
    case ("3", "policy 3 text"):
      self = .three
    default:
      return nil
    }
  }
}

At this point, I've figured out how to achieve similar functionality with a struct and an enum. The enum seems like a lot more maintenance if someone goes back to update it and it's more error prone. Paul Hegarty says line that won't crash is the line you don't write and the enum route looks and feels cumbersome.

Is there a memory advantage to going the enum route versus a struct?

When I'm done, I'd like to be able to pass around a Policy as a parameter, like so:

func test(for policy: Policy) {
  print(policy.rawValue.filename)
  print(policy.rawValue.text)
}

test(for: Policy.first)

Upvotes: 2

Views: 306

Answers (2)

Cristik
Cristik

Reputation: 32782

The only improvement by using an enum that I can think of is just simply replacing the struct keyword by an enum one:

enum Policy {
  static let first = PolicyType(filename: "firstFile.txt", text: "text in first file")
  static let second = PolicyType(filename: "secondFile.txt", text: "text in second file")
  static let third = PolicyType(filename: "thirdFile.txt", text: "text in third file")
}

Enums with no cases are impossible to instantiate, this way the developers can use only the defined static policy types.

A function like this:

func cantBeCalled(policy: Policy) { }

can't be called from your code since it's impossible to construct (allocate) a Policy.

Still, I'd keep the struct, and redesign everything into a single type:

struct Policy {
    static let first = Policy(filename: "firstFile.txt", text: "text in first file")
    static let second = Policy(filename: "secondFile.txt", text: "text in second file")
    static let third = Policy(filename: "thirdFile.txt", text: "text in third file")

    public let filename: String
    public let text: String

    private init(filename: String, text: String) {
        self.filename = filename
        self.text = text
    }
}

A struct maps better over your problem domain, as you need a container that will hold the two properties. Tuples are not good at scaling when the number of details grows, and are also harder to manipulate (they can't conform to protocols for example).

This new struct design has the same advantages like the above enum: can't construct policies "by hand", only a set of predefined policies are available. And comes with more benefits: only one type, better semantics for the container, ability to use protocols on the Policy type, no ambiguous accesses to the properties (the members of a labeled tuple can be accessed either via the label, or via the index).

Upvotes: 1

Daniel
Daniel

Reputation: 3597

Here's a possibility. It's a little lengthy, but it is Swifty:

enum Policy {
    case one, two, three

    var filename: String {
        switch self {
        case .one: return "Policy 1 name"
        case .two: return "Policy 2 name"
        case .three: return "Policy 3 name"
        }
    }

    var text: String {
        switch self {
        case .one: return "Policy 1 text"
        case .two: return "Policy 2 text"
        case .three: return "Policy 3 text"
        }
    }
}

The problem with Swift enums at the moment is that they are limited to RawValues. I recently encountered a similar situation as you, and I too tried to use an enum instead of a struct. I even experimented with a named tuple. But, I ended up using a struct.

Upvotes: 3

Related Questions