티스토리 뷰

iOS

[Swift] 열거형 (Enumerations)

Peppo 2022. 2. 9. 20:48
728x90

정의

열거형은 관련된 값들의 그룹에 대한 공통 타입을 정의하고 안전한 타입(type-safe)이 된 해당 값들로 코드 내에서 사용할 수 있게 해줍니다. 
각 열거 case 별로 값이 제공 된경우, 값의 타입은 String, Character, Int, Float가 될 수 있습니다.
열거형은 자체로 1급 클래스 유형 이어서 초기화를 정의 할 수도 있고, 초기기능을 확장할 수도 있고, 표준 기능을 제공하기 위해 프로토콜을 준수 할 수도 있습니다.

 

어렵다...


 

열거형 문법

열거형은 enum 키워드를 사용하여 정의 합니다.

enum Enumeration {
    // enumeration 정의는 여기에 !
}

 

다음은 (동, 서, 남, 북) 네 가지 방향을 갖는 CompassPoint 열거형 선언의 예시 입니다.

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

 

NOTE

Swift 열거형은 C나 Objective-C와 다르게 case 별로 integer 값을 할당 하지 않습니다.
예시의 CompassPoint를 보면, north, south, east, west가 암시적으로 0,1,2,3 값을 갖지 않고,
각 case 별로 명시적으로 정의된 타입의 고유한 값을 가집니다.

 

아래 처럼 여러 case를 한줄로 작성 할 수도 있습니다.

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

 

각 열거형 정의는 새로운 타입을 정의 하므로,

다른 타입들과 같이 열거형의 이름은 대문자로 시작하게 만들고, 단수형으로 이름을 짓습니다. 

var directionToHead = CompassPoint.west

 

directionToHead는 초기화 될때 CompassPoint의 값들중 하나로 타입 추론이 됩니다.

한번 directionToHead가 CompassPoint로 선언되면,
점 문법을(dot syntax) 이용해 값을 할당하는 축약형 문법을 사용할 수 있습니다.

directionToHead = .east

 


Switch 구문에서 열거형 값 매칭 
(Matching Enumeration Values with a Switch Statement)

 

각 열거형 값들을 switch 구문에 매칭 시킬 수 있습니다.

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

var directionToHead = CompassPoint.west  // 한번 선언 됨으로써 CompassPoint 값 인지함

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")
}
// Print: Watch out for penguins

 

switch문은 반드시 모든 경우들을 포함 해야 합니다. 

만약 위의 case에서 한 case라도 빠지게 될 경우 컴파일 되지 않습니다. 

모든 case를 일일히 작성하기 보다 예외 처리를 하고 싶다면 default case를 제공함으로써 처리되지 않는 case를 피할수 있습니다. 

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

// 예외처리 (default)
let somePlant = Planet.venus
switch somePlant {
case .earth:
    print("여기 사람 살아요")
default:
    print("여기 외계인 살아요!!")
}
// Print: 여기 외계인 살아요!!

// earth case
let somePlant = Planet.earth
switch somePlant {
case .earth:
    print("여기 사람 살아요")
default:
    print("여기 외계인 살아요!!")
}
// Print: 여기 사람 살아요

 

 


관련값 (Associated Values)

열거형의 각 case별로 custom type의 정보를 저장할 수 있습니다. 

아래 두 가지 바코드 예제를 보면서 설명을 하면,

upc

 

첫번째 바코드의 경우 아래 숫자를 (Int) 4가지 구분으로 나누어진 바코드 종류가 있고

QR코드

QR 코드의 경우 약 3,000개의 문자로 (String) 구성되어 있습니다. 

 

 

위 바코드 두 가지를 열거형으로 정의 하자면 아래와 같이 할 수 있습니다. 

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

 

위와 같은 정의는 실제로 정수나 문자열 값을 주지 않습니다.

상수, 변수화 시켜 Barcode.upc , Barcode.qrCode 중 하나일때 , 그와 관련된 값들의 타입만 정의 합니다.

 

upc

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

productBarcode로 불리는 새로운 변수를 만들고 Barcode.upc에 (8, 85909, 51226, 3) 튜플 값을 할당 합니다.

다른 바코드의 타입으로 할당 할 수도 있습니다.

 

qr 

productBarcode = .qrCode("ABCDEFGHIGKLMNOP")

위와 같이하면 Barcode.upc 였던 정수 값은 Barcode.quCode의 문자열 값으로 바뀝니다.

Barcode 타입의 상수, 변수는 upc나 qrCode로 저장 할 수는 있으나 한번에 둘 중 하나만 저장할 수 있습니다.

 

 

관련 값은 switch 문을 사용하여 상수, 변수로 선언 할 수 있습니다.

아래 처럼 변수/상수로 원하는 이름을 정의해서 사용할 수 있습니다.

// productBarcode = .qrCode("ABCDEFGHIGKLMNOP")

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)")
}
// QR code: ABCDEFGHIGKLMNOP

 

위 처럼 case안의 관련 값이 모두 상수거나 변수이면 let, var를 앞으로 빼내어 축약해 사용할 수 있습니다.

switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):  // let을 앞으로 이동
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check)")
case let .qrCode(productCode):                              // let을 앞으로 이동
    print("QR code: \(productCode)")
}
// QR code: ABCDEFGHIGKLMNOP

 


Raw 값 (Raw Values)

 

case에 기본값을 지정할 수 있습니다.

다음 예시는 ASCII 값을 열거형에 저장 하는 예제 입니다.

enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

 

위에서는 Character 타입으로 정의 했지만, 이 외에도 String, Int, Float등 타입을 사용할 수도 있습니다. 

각 Raw값은 열거형 선언 내에서 유니크한 값이어야 합니다. (중복 불가)

 

NOTE

Raw값은 관계값과 다릅니다. 
Raw값은 코드에서 열거형을 처음 선언할 때 정의 되어 항상 같은 값을 갖게 됩니다.
관계값은 같은 case라도 생성될 때 달라질 수 있습니다.

 

 

암시적으로 할당된 Raw값 (Implicitly Assigned Raw Values)

 

열거형을 다룰 때 raw값으로 String이나 Int 값을 사용할 수 있는데, raw값이 없을 경우. 

Swift에서는 자동으로 값을 할당해주기 때문에, 각 case별로 값을 할당해줄 필요는 없습니다. 

 

앞에 Planet 예시를 이어서 해보겠습니다.

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

print(Planet.mercury.rawValue)   // 1
print(Planet.earth.rawValue)     // 3

 

열거형 Planet에 Int 타입을 지정했고, 

mercury에 1이 라는 raw값을 할당했습니다. 

이후 case에는 암시적으로 raw값이 2 .. 3.. 4 순으로 증가 하게 됩니다. 

 

만약 String 타입을 지정하게 되면, 

case의 text가 자동으로 raw값으로 할당 됩니다.

 

아래 예시는 앞에 CompassPoint 예시를 이어 작업한 겁니다.

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

print(CompassPoint.west.rawValue) // west

 

 

Raw 값을 이용한 초기화 (Initializing from a Raw Value)

 

raw값을 이용하여 열거형 변수를 초기화 할 수 있습니다.

아래 예제는 raw값 4를 갖는 값을 열거형 변수의 초기 값으로 지정합니다. 

let possiblePlanet = Planet(rawValue: 4)   // mars
print(possiblePlanet)                      // Optional(__lldb_expr_36.Planet.mars)

 

 

모든 Int 값에 맞는 planet을 찾을수 있는게 아니기 때문에, raw값 초기자는 항상 optional 열거형 케이스를 반환합니다. 

if let possiblePlanet = Planet(rawValue: 4) {
    print(possiblePlanet)   // mars
} else {
    print("else")
}

 

 

만약 열거형에 없는 raw값을 초기자로 지정하면 nil 이 됩니다.

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

let planetPosition = 100

if let somePlanet = Planet(rawValue: planetPosition) {
    switch somePlanet {
    case .earth:        // let planetPosition = 3 일때 해당
        print("지구는 사람 사는곳")
    default:            // let planetPosition = 1,2,4,5,6 일때 해당
        print("사람 못 사는곳")
    }
} else {
    print("\(planetPosition)번째는 열거된 Planet에 없음")  
}

// 100번째는 열거된 Planet에 없음

 

 

재귀 열거자 (Recursive Enumerations)

 

재귀 열거자는 다른 열거 인스턴스를 관계 값으로 갖는 열거형 입니다.

case 앞에 indirect 키워드를 붙여서 표시합니다.

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

 

 

관계 값을 가지는 모든 열거형 caseindirect 키워드를 붙이고 싶다면 

enum 앞에 indirect를 붙여줍니다.

indirect enum Arithmetic {      // enum 앞에 indirect 추가
    case number(Int)
    case addition(Arithmetic, Arithmetic)
    case multiplication(Arithmetic, Arithmetic)
}

 

위 열거형은 세 가지 종류의 산술 표현식 (숫자, 덧셈, 곱셈)을 저장할 수 있습니다.

 

 

아래 예제는 ( 5 + 4 ) * 2 를 재귀 열거자로 표현한 예 입니다.

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

 

 

다음은 재귀 열거자를 처리 하는 함수 입니다.

func evaluate(_ expression: Arithmetic) -> Int {
    switch expression {
    case let .number(val):
        return val
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):      // left = 9 , right = 2
        return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
728x90