Tuesday, September 18, 2018

Swift 4 Introduction Series 1.14 - Enumeration

Enumeration

An enumeration grouped a set of related values into a common data type, so that we we can work with in a type-safe manner.


Define Enum

Syntax:


enum <name_enumeration> {
case <1st_enumeration_definition>
case <2nd_enumeration_definition>
case ...
}


enum <name_enumeration> {
case <1st_enumeration_definition>, <2nd_enumeration_definition>, ...


}


Note:
  • We can define enum with any data type.
  • Each enumeration define a new data type.
  • Please use first letter in capital to represent that it is a data type
  • Swift enumeration are not assigned a default value. We can reference the member directly.


Example


// we can define enum as follows
enum CompassPoint {
   case north
   case south
   case east
   case west
}




// We can also define enum using a single case statement with each member delimited by comma
enum Planet {
   case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}




Another example:


enum Month {
   case January, February, March, April, May, June, July, August, September, October, November, December
}




Using Enum

We can reference enum using dot to extract the member of the enum. See example below:


CompassPoint.north
CompassPoint.west

Planet.mars
Planet.uranus

Month.December
Month.July




We can define variable using enum and pass to other variable similar to Int or String:


let newPoint = CompassPoint.north
var anotherPoint = newPoint
anotherPoint = .south

var myPlanet: Planet
myPlanet = .neptune

let myMonth: Month
myMonth = .June
var theMonth = myMonth
theMonth = .December




Note:
  • We can use enum as data type and define constant or variable accordingly
  • When defining variable or constant using enum, a member must be specified.
  • If we transfer a defined data from constant to variable, we can switch to another member of the enum on the variable.
  • If the variable are define to the enum, subsequence switching we can use dot follow (.north) by member instead of specifying the name of enum (CompassPoint.north).

Using Switch with Enum

When using switch with enum, the only consideration we need to have is that we must consider all members of the enum while using switch. When it is not necessary to include all members, use default option.


Example:


enum CompassPoint {
   case north
   case south
   case east
   case west
}

// In the example below, all members are considered so default option is not necessary
let myPoint = CompassPoint.north

switch myPoint {
case .north:
   print("This is North")
case .south:
   print("Pointing South")
case .east:
   print("Pointing East")
case .west:
   print("Pointing Wast")
}




Another example:


enum Planet {
   case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}


// In the example below, NOT all members are considered so default option is required
let myPlanet = Planet.earth

switch myPlanet {
case .earth :
   print("Most beautiful planet")
default:
   print("Uninhabitable for human")
}




Iterating over Enum

This newly added feature allow us to iterate over all members of enum. To iterate over enum, we must add the keyword CaseIterable. The syntax is as follows:


enum <name_enumeration>: CaseIterable {
case <1st_enumeration_definition>, <2nd_enumeration_definition>, ...


}


By using CaseIterable, we have convert the enum to an array. We can apply all the functions that are application to array for enum. To access the array properties of the enum, we use allCases follow by member of array properties such as .count.
enum Planet: CaseIterable {
   case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let totalPlanet = Planet.allCases.count




Note: Please note that although we can use allCases to work enum as an array. However, further manipulation of the array will return the result as an array instead of enum.


We can use for in loop to iterate the enum:


enum Planet: CaseIterable {
   case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let totalPlanet = Planet.allCases.count

for planetItem in Planet.allCases {
   print(planetItem)
}




Enum for C/Objective C Programmer

When using enum for C or Objective C, all members are automatically associated with an integer start from 0. Officially Swift enumeration are not assigned a default value. We can use the member defined as a value in its own right. All we need to do is to reference the enum member directly. We can also compare member with member.


However, behind the scene, enum are still generated with hidden hash value starting from 0 and so on. We can access these default value using .hashValue. Using these hashvalue is not necessary since we can compare member against member instead of using the hash value.


Associated Value in Enum

We can use enum to store 2 or more different data type that are related to each other. For example, we have a database that store phone number in string and in number. We can create an enum that can support both data type.


The syntax to create associated enum is  slightly different. Syntax is as follows:


enum <name_enumeration> {
case <1st_enumeration_definition>(<datatype1, datatype2, ...>)
case <2nd_enumeration_definition>(<datatype1, datatype2, ...>)
case ...
}


Note:
  • Each enum member can have at least 1 or many data type definition.
  • When using enum with associated value, we can only use one member at a time. However, we can switch from one member to another member


Example:


enum Phone {
   case phoneNumber(Int)
   case phoneString(String)
}

func printPhone() {
   switch phone1 {
   case .phoneNumber (let number):
       print("The phone number for this user is \(number)")
   case .phoneString (let numberInSring):
       print("The phone number for this user is " + numberInSring)
   }
}

var phone1 = Phone.phoneNumber(82388976)
printPhone()
phone1 = Phone.phoneString("82739348")
printPhone()




Another example:
In another example, assuming that we have 2 different product code system. The first is a 8 digit number system. The second is 2 letter prefix follow by 6 digit code.


We can define the enum as follows:


enum InventoryCode {
   case old_system(String, Int)
   case new_code(Int)
}


We can switch between 2 different system in a variable as shown below:


enum InventoryCode {
   case old_system(String, Int)
   case new_code(Int)
}

var currentCode = InventoryCode.old_system("US", 192843)
print(currentCode)

currentCode = .new_code(82738764)
print(currentCode)




We can use switch to extract the details as shown below:


enum InventoryCode {
   case old_system(String, Int)
   case new_code(Int)
}

var currentCode = InventoryCode.old_system("US", 192843)

switch currentCode {
case .old_system(let prefix, let sixDigit):
   print("Under old system product code is \(prefix) follow by \(sixDigit)")
case .new_code(let eightDigit):
   print("Under new system product code is \(eightDigit)")
}




If there are multiple data in a member. Using let for each data can be quite tedious. We can use a single let for each switch statement.


enum InventoryCode {
   case old_system(String, Int, Int, Int, String)
   case new_code(Int)
}

var currentCode = InventoryCode.old_system("US", 212, 192843, 87, "BK")

switch currentCode {
case let .old_system(prefix, frontTwoDigit, sixDigit, backTwoDigit, suffix):
   print("Under old system product code is \(prefix) follow by \(frontTwoDigit) and \(sixDigit) and follow by \(backTwoDigit) and closed off by \(suffix)")
case .new_code(let eightDigit):
   print("Under new system product code is \(eightDigit)")
}




Raw Values in Enum

Although by default enum member are not associated with any values. However, we can set default values for each member. Such default values are known as raw values. The syntax of defining raw value is as follows:


enum <name_enumeration>:<raw_value_datatype> {
case <1st_enumeration_definition> = <raw_value>
case <2nd_enumeration_definition> = <raw_value>
case ...
}


Example:


enum Direction: String {
   case north = "Twelve o'clock"
   case south = "Six o'clock"
   case east = "Three o'clock"
   case west = "Nine o'clock"
}

let enemyDirection = Direction.west
print("Contact!!! Enemy \(enemyDirection.rawValue)!!!")




Another example:


enum Bearing: Int {
   case north = 360
   case south = 180
   case east = 90
   case west = 270
}

let shipDirection = Bearing.east
print("The ship will steer \(shipDirection.rawValue) degree in 15 minutes")




Notes:
Raw values are not the same as associated values. Associated values are defined with we define the variables, whereas we define the raw value when we construct the enum. Associated values changes when we create variables or constant. Raw values never change.


Assign Raw Values Automatically

We can ask the system to assign values automatically.


Example:


enum Planet: Int {
   case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let nextExploration = Planet.mars
print("The raw value of mars is \(nextExploration.rawValue)")
Planet.mercury.rawValue
Planet.neptune.rawValue




Since we define the enum with raw value but we failed to assigned it. The system will assigned the raw value starting from 0.


We can however define the starting value and let the system continue as shown below:


enum Month: Int {
   case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

let thisMonth = Month.June
print("June is written in numeric as \(thisMonth.rawValue).")




If we define any enum with string as the raw value type. The string equivalent of each member will be defined.


enum CompassPoint: String {
   case north
   case south
   case east
   case west
}

let direction = CompassPoint.west
print("The location of this building is at the " + direction.rawValue + " side of the town.")




We can also use raw value to initialize an enum variable or constant. By doing that, the initialized constant or variable will return as optional. We will use optional binding to check if the initialization is successful.


enum Month: Int {
   case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

if let nextMonth = Month(rawValue: 15) {
   print("Next month is \(nextMonth)")
} else {
   print("There is no such month.")
}


If the initialized value is less than 12, we should have proper result:


enum Month: Int {
   case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

if let nextMonth = Month(rawValue: 7) {
   print("Next month is \(nextMonth)")
} else {
   print("There is no such month.")
}



Important Note

Please note that enum in Swift is more power and it can incorporate many functionality.  Please take note that the above implementation of enum is not comprehensive. Please look out for more advanced topic to understand enum in Swift.

***

No comments:

Post a Comment