Apple 제공 Swift 프로그래밍 가이드(3.0.1)의 Enumerations 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.
들어가며
Enumeration(이하 ENUM)은 관련된 값들을 그룹으로 묶어서 common type 으로 정의해준다. ENUM을 이용하면 타입에 안전한(type-safe) 코드를 작성할 수 있다.
C에서의 ENUM은 각 케이스마다 Integer 값을 할당했었다. 이것을 raw value라고 한다. Swift에서의 ENUM은 raw value로 여러가지 타입을 사용할 수 있고 심지어 항상 raw value 를 할당하지 않아도 된다. 또한 케이스 각각에 연관된 값들(associated values)을 지정할 수 있다.
Swift에서의 ENUM은 기존에는 class에서만 지원되던 기능들을 많이 제공해준다. 예를들면, 계산된 프로퍼티(computed properties), 인스턴스 메서드 등이 있다. 또한 ENUM의 이니셜라이저를 정의할 수도 있고, 기능을 확장시킬 수도 있고, 특정 프로토콜을 따르게 할 수도 있다. 자세한 것은 나중에 살펴보자.
Enumeration Syntax
enum 키워드를 통해 정의할 수 있다.
case 키워드를 통하여 enumeration case 들을 정의할 수 있다.
enum CompassPoint { // 대문자로 시작하게끔
case north // c, objective-c 와 다르게 자동으로 0부터 채워지지 않는다
case south
case east
case west
}
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn,
uranus, neptune
// 콤마로 구분하여 정의 가능
// 복수형말고 단수형이 자명하다
}
이렇게 정의한 ENUM을 변수에 할당하는 법을 알아보자.
var directionToHead = CompassPoint.west
// 변수가 CompassPoint ENUM 타입이라고 유추되는 타이밍은
// west 케이스로 초기화가 될 때이다
directionToHead = .east
// 한 번 타입이 판단된 변수는 이런 식으로
// ENUM 이름을 생략하고 값을 할당할 수 있다
Matching Enumeration Values with a Switch Statement
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"
// <주의> ENUM의 모든 케이스를 포함할 수 있어야 한다.
// 그게 안 된다면 default 케이스를 사용하자.
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// prints "Mostly harmless"
Associated Values
ENUM의 케이스 각각에 연관된 다른 타입의 값들을 저장할 수 있다. 따라서 필요한 추가정보를 저장할 수 있으며, ENUM 변수에 접근할 때마다 이 정보를 변경하는 것도 가능하다. 이 값을 associated value라고 부른다. associated value 의 타입과 개수는 케이스 별로 각각 상이하게 정의되어도 된다.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
// 아래와 같이 사용
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
productBarcode = .qrCode // 컴파일에러
// <추가설명>
let qrCodeBarcode = Barcode.qrCode
// 이 상수의 타입은 (String) -> Barcode
let myQrCode = qrCodeBarcode("ABCDEFGHIJKLMNOP")
// 이 상수의 타입은 Barcode이고 값은 qrCode("ABCDEFGHIJKLMNOP")
// let과 var로 associated value를 추출할 수 있다
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: ABCDEFGHIJKLMNOP."
// 튜플 자체를 한번에 let 또는 var로 받아도 된다
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: ABCDEFGHIJKLMNOP."
// <참고> 만약 case let .upc(values): 로 받으면
// values는 4개 요소로 구성된 튜플이다
Raw Values
C에서 했던 것처럼 Default value(raw value)로 ENUM의 케이스를 채울 수도 있다. 이 경우에는 모든 케이스의 값의 타입이 일치해야 한다.
enum ASCIIControlCharacter: Character {
// raw value의 type을 필수로 명시해주어야 한다!
// 여기서는 Character 라고 정의
case Tab = "\t"
case LineFeed = "\n" // raw value의 타입은 모든 케이스가 같아야
case CarriageReturn = "\r" // 그리고 케이스 별로 유니크해야
}
print(
ASCIIControlCharacter.Tab.rawValue) // "\t"
raw value는 꼭 Integer 값이 아니라 String, Character, Integer, Floating-Point 등 여러 타입을 사용할 수 있다.
<note> Raw Value는 ENUM을 처음 정의한 순간부터 들어가있는 디폴트 값이고 Associated Value는 ENUM의 케이스를 가지고 변수/상수를 만들 때마다 셋팅이 되는 값이다. 이 두개는 다른 것이다.
<참고> enum 선언 시 rawValue의 타입 지정을 하지 않은 경우에는 케이스에 대고 rawValue를 부르지 못한다. 그런 멤버가 없다는 오류가 난다.
<Implicitly Assigned Raw Values>
명시적으로 전부 raw value를 할당하지 않더라도, raw value의 type만 명시해준다면 컴파일러가 알아서 raw value를 할당해준다.
enum Planet: Int {
case mercury, venus, earth, mars, jupiter
// 0, 1, 2, 3, 4
}
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter
// 1, 2, 3, 4, 5
}
enum Planet: Int {
case mercury=1, venus, earth, mars=10, jupiter
// 1, 2, 3, 10, 11
}
enum Planet: Int {
case mercury, venus = 6, earth, mars, jupiter
// 0, 6, 7, 8, 9
}
enum Planet: Int {
case mercury, venus = 0, earth, mars, jupiter
// 컴파일 에러 (raw value가 유니크하지 않다며)
// 0, 0, 1, 2, 3 이 되니까
}
enum Planet: Int {
case mercury = 6, venus = 5, earth, mars, jupiter
// 컴파일 에러 (raw value가 유니크하지 않다며)
// 6, 5, 6, 7, 8, ... 이 되니까
}
raw value 타입이 String 인 경우에는 default Raw Value 값이 Case 이름 자체이다.
enum CompassPoint: String {
case north, south, east, west
}
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
// <추가설명>
enum CompassPoint: String {
case north, south = "north", east, west
// raw value가 unique하지 않기 때문에 컴파일 에러
}
<Initializing from a Raw Value>
rawValue가 있는 ENUM은 rawValue를 파라미터로 넘기는 이니셜라이저를 사용할 수 있다.
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 의 타입은 Planet?
// rawValue가 7인 Type이 정의되어 있지 않다면 nil이 반환된다
// 이렇게 nil이 될 수 있는 이니셜라이저를 failable initializer 라고 한다
// 옵셔널 바인딩을 함께 활용
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
recursive enumeration 이란 한 개 이상 케이스의 associated value로 ENUM의 인스턴스를 받는 ENUM을 말한다. indirect 라는 키워드를 사용하여 만든다.
// 다음과 같이 필요 케이스 앞에 indirect 키워드를 붙여도 되고
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 이렇게 enum 옆에 indirect 를 명시하면 모든 case에 적용
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
이것을 이용하는 계산기 예제를 보자.
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)
}
}
// evaluate (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))
print(evaluate(product))
// prints "18"
'Swift 공식 가이드 > Swift 3' 카테고리의 다른 글
Properties (0) | 2017.02.27 |
---|---|
Classes and Structures (0) | 2017.02.27 |
Closures (0) | 2017.02.21 |
Functions (0) | 2017.02.19 |
Control Flow (0) | 2017.02.15 |