Enumeration

woo94·2023년 2월 4일
0

swift

목록 보기
2/5

Intro

Swift에 있어서 가장 많이 사용되는 type은 아마도 structenum이지 싶다. 다른 언어를 사용하다 온 사람으로써는 enum의 광범위한 사용처에서 어리둥절 할수도 있다(나 또한 그랬다 😓).
Swift의 enumeration은 first-class type으로 전통적으로 class로만 지원되는 특징들을 도입한다. Computed properties나 instance method, initializer, extension을 통한 확장과 protocol을 따르는 등을 할 수 있다.

Enumeration Syntax

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

Enumeration에 정의된 value들(north, south, east, west)는 각자의 enumeration case들이다. case keyword를 사용하여 새로운 enumeration case를 추가한다.
(Swift enumeration은 C와 Objective-C와는 달리 기본값으로 정수값을 가지지 않는다.)

다수의 case들을 comma로 분리된 한 줄로 표현할수도 있다:

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

각각의 enumeration에 대한 정의는 새로운 type을 정의한다.

var directionToHead = CompassPoint.west

directionToHead의 type은 CompassPoint의 possible value 중 하나로 초기화 되면서 type에 대한 추론이 가능하게 된다. 한번 directionToHeadCompassPoint로 정의되면 다른 CompassPoint value로 설정하는데에는 shorter dot syntax를 통해서 가능하다:

directionToHead = .east

Matching Enumeration Values with a Switch Statement

각각의 enumeration value를 switch statement를 사용하여 match 시킬 수 있다:

directionToHead = .south

switch directionToHead {
case .north:
	print("Lots of planets have a north")
case .south:
	print("Watch out for penguins")
case .east:
	print("Where the sun rises")
case .west:
	print("Where the skies are blue")
}
// Prints "Watch out for penguins"

Switch statement는 반드시 exhaustive 해야한다. 만약 .west의 case가 생략되었다면 이 코드는 compile 되지 않는다. Exhaustiveness를 요구하는 것은 enumeration case가 실수로 생략되는 것을 방지해준다.

모든 enumeration case에 대하여 적절한 case를 제공해줄 수 없다면, default case를 제공해줄 수 있다. 이것은 명시적으로 설정되지 않은 모든 case들을 커버한다:

let somePlanet = Planet.earth
switch somePlanet {
case .earth:
	print("Mostly harmless")
default:
	print("Not a safe place for humans")
}
// Prints "Mostly harmless"

Iterating over Enumeration Cases

어떤 enumeration에 대해서는 enumeration의 모든 case를 담은 collection을 가지는 것이 유용할때가 있다. :CaseIterable 을 enumeration의 이름 뒤에 적어주면 가능하다. Swift는 모든 case들을 담은 collection을 enumeration type의 allCases property로 이것을 외부에 노출시켜준다:

enum Beverage: CaseIterable {
	case coffee, tea, juice
}

let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"

allCases를 여타 다른 collection들 처럼 사용할수도 있다. Collection의 요소들은 enumeration type의 instance들이므로, 이 경우에는 Beverage value들이다.

for beverage in Beverage.allCases {
	print(beverage)
}

Associated Values

Planet.earth에 constant나 variable을 설정하고 후에 이 값을 확인해볼 수 있다. 이러한 부가적인 정보를 associated value라고 부른다.
Swift enumeration은 어떤 주어진 type의 값이든 저장할 수 있도록 정의할 수 있고, 만약 필요하다면 각각의 case 별로 다른 value type을 저장할수도 있다. 이러한 enumeration은 다른 프로그래밍 언어들에서는 discriminated unions, tagged unions, variant 등으로 불린다.

예를들어 inventory tracking system에서 product를 track하기 위해서 2가지 종류의 barcode를 사용한다고 가정해보자. 어떤 물건들은 1D barcode가 UPC format으로 표시되어있을 수 있다. 이것은 1. number system digit, 2. manufacturer code digit, 3. product code digit, 4. check digit으로 이루어져 있다:

  1. number system digit: 8
  2. manufacturer code digit: 85909
  3. product code digit: 51226
  4. check digit: 3

다른 물건들은 2D barcode가 QR code format으로 표시되어 있을 수 있다:

Inventory tracking system에서는 UPC barcode는 4개의 정수로 이루어진 tuple을, QR barcode는 string 형태로 저장하면 편리하다.

enum Barcode {
	case upc(Int, Int, Int, Int)
    case qrCode(String)
}

barcode의 값을 다음과 같이 저장할 수 있다:

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("DKLGJDKLSJE")

이전과 같이 switch statement를 사용하여 다른 종류의 barcode type을 확인할 수 있다. 하지만 이번에는 associated value가 switch statement에 의해서 추출되어야 한다. 이들을 let prefix로 constant로 추출하거나 var prefix로 variable로 추출할 수 있다:

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
	print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
	print("QR code: \(productCode).")
}

//Prints "QR code: DKLGJDKLSJE"

단순한 표기를 위해 case name의 앞에 하나의 var나 let annotation을 위치시켜 모든 값들을 상수나 변수로 추출할수도 있다:

switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
	print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
	print("QR code: \(productCode).")
}

//Prints "QR code: DKLGJDKLSJE"

Raw Values

Associated value의 대안으로 enumeration이 단일한 타입의 default value(raw value)와 함께 생성되는 것이다.

enum ASCIIControlCharacter: Character {
	case tab = "\t"
	case lineFeed = "\n"
	case carriageReturn = "\r"
}

ASCIIControlCharacter enum의 raw value는 Character type으로 정의되었다. :Character를 표기해주지 않으면 compile이 되지 않는다: Enum case cannot have a raw value if the enum does not have a raw type

❗️ Raw value는 associated value와는 다른것이다. Raw value는 enumeration을 처음 정의하였을 때 설정되는 prepopulated value이다. 특정한 enumeration case에 대해서의 raw value는 언제나 똑같다. Associated value는 enumeration case에 따라 새로운 상수나 변수를 생성할때 설정된다.

Raw value는 string, character, 혹은 integer나 floating-point number type이 가능하다.

Implicitly Assigned Raw Values

Integer나 String raw value를 저장하는 enumeration이라면 각각의 case에 매번 직접 raw value를 명시적으로 할당해주지 않아도 된다. Swift가 자동으로 값을 할당해준다.

예를들어 integer가 raw value에 사용되었다면, 암시적으로 각각의 case는 이전의 case 보다 1이 큰 값을 가진다. 만약 첫번째 case가 value를 가지고 있지 않다면, 그 값은 0이다.

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

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

Planet.venus는 2의 raw value를 가지고 있는 식이다.

String이 raw value로 사용이 되면, implicit value는 case의 name text 이다.

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"

Initializing from a Raw Value

Enumeration을 raw-value type으로 정의했다면, enumeration은 자동으로 raw value type의 값을 받아서 enumeration case나 nil을 반환하는 initializer를 가지게 된다. Enumeration의 새로운 instance를 만들기 위해서 이 initializer가 사용할수도 있다.

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
	switch somePlanet {
	case .earth:
		print("Mostly harmless")
	default:
		print("Not a safe place for humans")
	}
} else {
	print("There isn't a planet at position \(positionToFind)")]
}

// Prints "There isn't a planet at position 11"

Recursive Enumerations

하나 혹은 그보다 더 많은 enumeration case에 대하여 enumeration의 또다른 instance를 가지게 하는 것을 의미한다. Enumeration case가 recursive하다는 것은 그 앞에 indirect를 써주어서 표시하면 된다. Indirection을 위한 필수적인 layer를 compiler가 추가해준다.

enum ArithmeticExpression {
	case number(Int)
	indirect case addition(ArithmeticExpression, ArithmeticExpression)
	indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

indirect를 enumeration의 정의부분에 적어서 모든 associated value를 가지는 enumeration case에 대해서 indirection을 가능하게 해줄수도 있다:

indirect enum ArithmeticExpression {
	case number(Int)
	case addition(ArithmeticExpression, ArithmeticExpression)
	case multiplication(ArithmeticExpression, ArithmeticExpression)
}

enumeration은 3가지 종류의 arithmetic expression을 저장할 수 있다: 숫자, 두 expression의 합, 두 expression의 곱이다. addition과 multiplication case들은 associated value로 arithmetic expression을 가지고 있어서 nest expression을 가능하게 해준다. Data가 nest되어있기 때문에 data를 저장하기 위한 enumeration 또한 nesting을 지원해주어야 한다 - 즉 enumeration은 재귀적이어야 한다.

ArithmeticExpression을 사용하여 (5 + 4) * 2 를 재귀적으로 표현한 아래의 예시를 보자:

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_ expression: ArithmeticExpression) -> Int {
	switch expression {
	case let .number(value):
		return value
	case let .addition(left, right):
		return evaluate(left) + evaluate(right)
	case let .multiplication(left, right):
		return evaluate(left) * evaluate(right)
	}
}

print(evaluate(product))
// Prints "18"
profile
SwiftUI, node.js와 지독하게 엮인 사이입니다.

0개의 댓글

관련 채용 정보