The enumeration

  • Structs and classes are both record types. A record consists of zero or more fields (attributes) that have a type. A tuple is also a record type: it’s actually a lightweight anonymous structure with less functionality. Swift’s enumeration falls into an entirely different category, sometimes called a tag union or variant type.

An overview of the

  • An enumeration consists of zero or more cases, each of which can have a tuple-style list of associated values.

Enumerations are value types

Just like structures, enumerations are value types. Its capabilities are almost identical to those of the structure:

  • Enumerations can have methods, evaluate properties, and subscript operations.
  • Methods can be declared mutable and immutable.
  • You can implement extensions for enumerations.
  • Enumerations can implement a variety of protocols.

But enumerations cannot have storage properties. The state of an enumeration is represented entirely by a combination of its members and their associated values. For a particular member, the associated value can be treated as its storage property.

Sum type and product type

  • An enumeration value will contain only one enumeration member (plus the associated value if the member has an associated value).
  • In general, the number of residents of a tuple (or structure, or class) is equal to the product of the number of residents of its members. Therefore, structures, classes, and tuples are also called Product Types.
  • In general, the number of inhabitants of an enumeration is equal to the sum of the inhabitants of all its members. Hence the reason enumerations are called Sum Types.

Pattern matching

  • Pattern matching is not unique to switch, but it is the most obvious use case.

  • Modes supported by Swift:

    Wildcard mode – The symbol is underscore (_). It matches any value and ignores it.

    Tuple pattern – matches tuples with a comma-separated list of subpatterns.

    Enumerator pattern – Matches the specified enumerator. It can include subpatterns to handle associated values, such as equality checking (.success(42)) or value binding (.failure(let error)).

    Value binding mode – Binds part or all of a matched value to a new constant or variable.

    Optional value pattern – provides a syntactic sugar for matching and unpacking optional values using the familiar question mark syntax.

    Type conversion pattern — Pattern is SomeType A value’s runtime type must be SomeType or a subclass of it.

    Expression patterns – Matches expressions by passing input values and patterns as parameters to the pattern matching operator (~=) defined in the standard library.

Pattern matching in other contexts

  • While pattern matching is the only way to extract associated values from enumerations, it is not specific to enumerations or switch statements.

Use enumerations for design

The completeness of Swift statements

  • A switch statement must be complete.
  • The biggest benefit of completeness checking is if you want enumerations to evolve synchronously with the code that uses them.

It is impossible to create an illegal state

Why use a statically typed language like Swift. Performance is one of them: the more the compiler knows about the types of variables in the program, the faster code it produces.

An equally important reason is that the type system can guide developers on how they should use the API.

Use enumerations to implement states

  • The set of states that a system can exist is also called its state space.
  • Try to keep your state space as small as possible.
  • Enumerations are not completeFinite-state machines (FINite-State machines)Because it lacks the ability to specify illegal state transitions.

Choose between enumerations and structs

  • If we make the structure’s initialization method access level internal or public, we can extend the structure by adding static methods or properties to other files or even other modules to add new analysis events to the API. The version of the enumeration does not do this: you cannot add new members to the enumeration elsewhere.
  • Enumerations allow for more precise implementation of data types; It can only represent one of the predefined members, but the structure can represent an infinite number of values because of these two attributes. The precision and security of enumerations comes in handy if you want to do further processing on events (for example, merge sequences of events).
  • A structure can have private “members” (that is, static methods or static properties that are not visible to all consumers), and the visibility of the members in an enumeration is always the same as the enumeration itself.
  • You can use the switch statement for enumerations and take advantage of the completeness of the statement to ensure that no event type is missed. Because of this rigor, adding a new event type to the enumeration might break the source code for users of the API, but you can add static methods to the structure for a dozen new types without worrying about affecting the rest of the code.

Similarities between enums and protocols

  • Enumerations and protocols can both represent “one” relational structures.
  • With an inclusionThe shape of the typeRendering methodsShapeFor example, enumerations and protocols differ in organizing code. Enumeration-based implementations are grouped by method: the CGContext rendering code for all shape types is in a single switch statement in the Render (into:) method. Protocol-based implementations, on the other hand, are grouped by “members” : each concrete type implements its own Render (into:) method, which contains each shape-specific rendering code. This leads to a difference in extension dimensions, with enumerations being implemented to facilitate the addition of rendering methods, and protocols being implemented to facilitate the addition of shapes.

Use enumeration to implement recursive data structures

  • Enumerations are great for implementing recursive data structures, that is, data structures that “contain” themselves.

    /// a one-way linked list
    enum List<Element> {
      case end
      indirect case node(Element, next: List<Element>)}Copy the code

    Note the indirect keyword, which is required for the code to compile through. The indirect tells the compiler to represent the Node member as a reference, thereby making the recursion work. The indirect syntax applies only to enumerations.

Raw Value

Specifying a raw value for an enumeration requires that the enumeration name be followed by the type of the original value separated by a colon. Each member is then assigned an original value using copy syntax.

enum HTTPStatus: Int {
  case ok = 200
  case created = 201
  case movedPermanently = 301
  case notFound = 404
}
Copy the code

The original value of each member must be unique.

RawRepresentable agreement

A type of protocol implementing RawRepresentable gets two new apis: a rawValue property and a failable initialization method (init? (rawValue:)).

/// a type that can be converted to the associated original value
protocol RawRepresentable {
  /// The type of the original value, such as Int or String.
  associatedtype RawValue
  
  init?(rawValue: RawValue)
  var rawValue: RawValue { get}}Copy the code

Manually implement RawRepresentable

enum AnchorPoint {
  case center
  case topLeft
  case topRight
  case bottomLeft
  case bottomRight
}

extension AnchorPoint: RawRepresentable {
  typealias RawValue = (x: Int, y: Int)
  
  var rawValue: (x: Int, y: Int) {
    switch self {
      case .center: return (0.0)
      case .topLeft: return (-1.1)
      case .topRight: return (1.1)
      case .bottomLeft: return (-1.-1)
      case .bottomRight: return (1.-1)}}init?(rawValue: (x: Int,y: Int)) {
    switch rawValue {
      case (0.0) :self = .center
      case (-1.1) :self = .topLeft
      case (1.1) :self = .topRight
      case (-1.-1) :self = .bottomLeft
      case (1.-1) :self = .bottomRight
      default: return nil}}}Copy the code

Let structures and classes implement RawRepresentable

struct UserID: RawRepresentable {
  var rawValue: String
}
Copy the code

An internal representation of the original value

An instance of an enumerated type can have only one of its members. The only way to get the rawValue is by calling rawValue and init, okay? (rawValue:) both apis.

Enumeration value

The need to operate on the residents of a class as a collection is usually very user specific, for example, iterating through or counting them. The Caselterable protocol implements this functionality by adding a static attribute, allCases.

/// A type that provides a set of all its values
protocol Caselterable {
  associatedType AllCases: Collection
  	where AllCases.Element = = Self
  
  static var allClases: AllCases { get}}Copy the code

Manually implement Caselterable

extension Bool: Caselterable {
  public static var allCases: [Bool] {
    return [false.true]}}Bool.allCases // [false, true]
Copy the code

Fixed and unfixed enumerations

  • Enumerations that may add new members in the future are called immutable.
  • @Unknown Default gives you the best of both worlds: compile time completeness checking and run time security.
  • @frozen is used to declare a particular enumeration as fixed. By using this property, the library developer is making a promise never to add new members to the tagged enumeration – otherwise doing so would break binary compatibility.
  • Examples of fixed enumerations in the standard library includeOptionalResult; If they are not fixed, switching them always requires a default clause, which can be a big annoyance.

Tips and tricks

  • Try to avoid using nested switch statements.
  • Use the definite initialization check.
  • Avoid naming members with None or some.
  • Backtick members named with reserved keywords.
  • Members can be used just like factory methods.
  • Do not use associated values to simulate storage properties. Please use structure instead.
  • Do not overuse associated value components.
  • Use empty enumerations as namespaces.