Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

주니곰의 괴발노트

Swift - 열거형 본문

iOS

Swift - 열거형

Jhe2 2023. 3. 12. 21:56

정의

  • 관련된 값의 그룹을 위한 일반 타입을 정의하고 코드에서 Type-Safe 방법으로 값을 동작하게 함
  • Swift에서의 열거형은 훨씬 유연하고 열거형의 각 케이스에 값을 제공하지 않아도 됨
  • 값 (원시값) 이 각 열거형 케이스로 제공된다면 그 값은 문자열, 문자 또는 정수 또는 부동 소수 타입일 수 있음
  • 열거형 케이스는 각 다른 케이스 값으로 저장될 모든 타입의 관련된 값을 지정할 수 있음
  • 하나의 열거형의 일부로 관련된 케이스의 공통 세트를 정의할 수 있으며 각각은 연관된 적절한 타입의 다른값 세트를 가지고 있음
  • Swift의 열거형은 그 자체로 1급 타입
  • 열거형의 현재값에 대한 추가 정보를 제공하는 계산 프로퍼티와 열거형이 나타내는 값과 관련된 기능을 제공하는 인스턴스 메서드와 같이 전통적으로 클래스에서만 지원되는 많은 기능을 채택
  • 열거형은 케이스 초기값을 제공하기 위해 초기화를 정의할 수도 있음
  • 열거형은 기존 구현을 넘어 기능적으로 확장될 수도 있고 표준 기능을 제공하기 위해 프로토콜을 준수할 수 있음
 

열거형 구문

enum SomeEnumeration {
    // enumeration definition goes here
}
  • 열거형은 enum 키워드와 중괄호 안에 모든 정의를 위치시켜 나타냄

 

enum CompassPoint {
    case north
    case south
    case east
    case west
}
  • 열거형 안에 정의된 값 (north, south, east, west)은 열거형 케이스 (enumeration cases)
  • 새로운 열거형 케이스를 나타내기 위해 case 키워드를 사용
  • Swift 열거형 케이스는 C와 Objective-C 처럼 기본적으로 정수값을 설정하지 않으므로 CompassPoint 에서 northsoutheastwest 는 0123 과 같지 않음
  • 열거형 케이스는 CompassPoint 의 명시적으로 정의된 타입으로 자체 값

 

 

enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
  • 여러개의 케이스는 콤마로 구분하여 한줄로 표기 가능
  • 각 열거형 정의는 새로운 타입으로 정의
  • Swift의 다른 타입처럼 대문자로 시작하는 그들의 이름 (CompassPoint 와 Planet)이 타입

 

var directionToHead = CompassPoint.west
  • directionToHead 타입은 CompassPoint 의 가능한 값 중 하나로 초기화 될 때 유추됨

 

directionToHead = .east
  • directionToHead 는 CompassPoint 로 선언되고 더 짧게 점 구문을 사용하여 다른 CompassPoint 값을 설정 가능
  • directionToHead 의 타입은 이미 알고 있으므로 값을 설정할 때 타입을 삭제할 수 있음

 

Switch 구문에서 열거형 값 일치

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 구문으로 각각의 열거형 값을 일치시킬 수 있음
  • switch 구문은 열거형 케이스를 고려할 때 완벽해야 함
  • .west 에 대한 case 가 생략된다면 CompassPoint 케이스에 대해 모든 리스트이 고려되지 않았으므로 이 코드는 컴파일 되지 않음
  • 완전성을 요구하면 열거형 케이스가 실수로 생략되지 않도록 함
 
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("Mostly harmless")
default:
    print("Not a safe place for humans")
}
// Prints "Mostly harmless"
  • 모든 열거형 케이스에 대해 case 를 제공하는 것이 적절하지 않은 경우 명시적으로 해결되지 않은 사례를 포함하는 default 케이스를 제공할 수 있음

 

열거형 케이스 반복

enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"

for beverage in Beverage.allCases {
    print(beverage)
}
// coffee
// tea
// juice
  • 일부 열거형의 경우 열거형의 모든 케이스를 수집하는 것이 유용
  • 열거형 이름 뒤에 : CaseIterable 을 작성하여 활성화
  • Swift는 열거형 타입에 allCases 프로퍼티로 모든 케이스를 수집하고 방출
  • Beverage 열거형에 모든 케이스를 포함하는 콜렉션에 접근하기 위해 Beverage.allCases 를 작성
  • 다른 콜렉션 처럼 allCases 를 사용할 수 있음
  • 콜렉션의 요소는 열거형 타입의 인스턴스 이기 때문에 이 경우에는 Beverage 값들
  • 위의 예제는 얼마나 많은 케이스가 존재하는지 계산하고 아래의 예제는 for-in 루프를 사용하여 모든 케이스를 반복합니다.

 

연관값

  • 상수 또는 변수를 Planet.earth 로 설정하고 나중에 값을 체크할 수 있음
  • 그러나 경우에 따라 이러한 케이스 값과 함께 다른 타입의 값을 저장할 수 있는 것이 유용
  • 이 추가적인 정보를 연관값 (associated value) 이라고 하며 해당 케이스를 코드에서 값으로 사용할 때마다 달라짐
  • 주어진 타입의 연관된 값을 저장하기 위해 Swift 열거형을 정의할 수 있고, 이 값 타입은 필요에 따라 열거형의 각 케이스에 따라 달라질 수 있음
 

  • 재고 추적 시스템이 2가지 타입의 바코드로 제품을 추적해야 된다고 가정
  • 어떤 제품은 숫자 0 에서 9 를 사용하는 UPC 형식의 1D 바코드 라벨이 부착
  • 각 바코드에는 숫자 시스템과 이어서 5자리의 제조업체 코드와 5자리의 제품 코드가 있음
  • 다음에는 코드가 올바르게 스캔되었는지 확인하기 위해 검사 숫자가 있음
  • 다른 제품은 어떠한 ISO 8859-1 문자도 사용할 수 있고 2,953개의 문자까지 인코딩할 수 있는 QR 코드 형식의 2D 바코드 라벨이 부착

 

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
  • 재고 추적 시스템은 UPC 바코드를 4개의 정수로 된 튜플로 저장하고 QR 코드 바코드를 모든 길이의 문자열로 저장하는 것이 편리
  • (Int, Int, Int, Int) 타입의 연관된 값을 가진 upc 또는 String 타입의 연관값을 가진 qrCode 를 취할 수 있는 Barcode 라는 열거형 타입을 정의
  • 이 정의는 어떠한 실질적인 Int 또는 String 값을 제공하지 않음
  • 이것은 단지 Barcode.upc 또는 Barcode.qrCode 와 같을 때 Barcode 상수와 변수에 저장할 수 있는 연관값의 타입을 정의

 

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
  • 이러한 타입을 이용하여 새로운 바코드를 생성 가능
  • productBarcode 라 불리는 새로운 변수를 생성하고 연관된 튜플값인 (8, 85909, 51226, 3) 을 Barcode.upc 의 값으로 할당
  • 같은 상품의 다른 바코드 타입을 할당 가능
  • 여기서 기존의 Barcode.upc 와 기존 변수의 정수 값은 새로운 Barcode.qrCode 와 해당 값의 문자열 값으로 대체
  • Barcode 타입의 상수와 변수는 .upc 또는 .qrCode 모두 이것들과 연관된 값으로 저장할 수 있지만 오직 하나의 값만 저장할 수 있음
 
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."
  • 스위치 구문을 이용하여 다른 바코드 타입을 확인할 수 있음
  • 그러나 이번에는 관련값이 스위치 구문의 일부로 추출
  • switch 케이스의 본문 내에서 사용하기 위해 상수 (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."​
  • 열거형 케이스를 위한 연관된 값 모두 상수로 추출하거나 변수로 추출하려면 간결하게 케이스 이름 앞에 var 또는 let 을 선언

 

원시값

  • 연관된 값의 대안으로 열거형 케이스는 모두 동일한 타입의 기본값 (원시값 (raw values))으로 미리 채워질 수 있음

 

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
  • ASCIIControlCharacter 라 하는 열거형의 원시값은 Character 타입으로 정의되고 일반적인 ASCII 제어 문자 중 일부를 선정
  • 원시값은 문자열, 문자, 정수 또는 부동소수점 숫자 타입이 가능
  • 각 원시값은 열거형 선언부 내에서 유일한 값이어야 함
  • 원시값은 연관값 (associated values)과 같지 않음
  • 원시값은 위의 3개의 ASCII 코드처럼 코드에서 열거형을 처음 정의할 때 미리 설정하는 값
  • 특정 열거형 케이스를 위한 원시값은 항상 같음
  • 연관값은 열거형 케이스 중 하나를 기반으로 새로운 상수 또는 변수를 생성할 때 설정하고 달라질 수 있음

 

암시적으로 할당된 원시값

  • 정수 또는 문자열 원시값이 저장된 열거형으로 동작 시 각 케이스에 명시적으로 원시값을 가질 필요가 없음
  • 원시값을 설정하지 않으면 Swift는 자동적으로 값을 할당
  • 예를 들어 정수를 원시값으로 사용하면 각 케이스의 암시적 값은 이전 케이스보다 하나씩 증가
  • 첫번째 케이스에 값 설정이 안되어 있으면 0 으로 설정

 

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
  • Planet.mercury 는 명시적으로 원시값 1 을 가지고 Planet.venus 는 암시적으로 원시값 2 를 가짐
  • 원시값으로 문자열이 사용될 때 각 케이스의 암시적 값은 케이스의 이름의 문자

 

enum CompassPoint: String {
    case north, south, east, west
}​
 
  • CompassPoint.south 는 "south" 의 암시적 원시값을 가지고 있음
 
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"​
  • rawValue 프로퍼티를 사용하여 열거형 케이스의 원시값에 접근 가능
 

원시값으로 초기화

  • 원시값 타입으로 열거형을 정의한다면 열거형은 원시값의 타입 (rawValue 라는 파라미터) 을 사용하는 초기화를 자동으로 수신하고 열거형 케이스 또는 nil 을 반환
  • 이 초기화를 이용하여 열거형의 새 인스턴스를 만들 수 있음

 

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
 
  • 7 원시값으로 천왕성을 식별
  • 그러나 가능한 모든 Int 값으로 행성을 찾지 않기 때문에 원시값 초기화는 항상 옵셔널 열거형 케이스를 반환
  • 위의 예제에서 possiblePlanet 은 Planet? 또는 옵셔널 Planet 타입
  • 원시값 초기화는 모든 원시값을 열거형 케이스로 반환할 수 없으므로 초기화가 실패할 수 있음
 
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"
  • 11 의 위치로 행성을 찾는다면 원시값 초기화로 부터 반환된 옵셔널 Planet 값은 nil
  • 11 의 원시값으로 행성을 찾기위해 옵셔널 바인딩을 사용
  • if let somePlanet = Planet(rawValue: 11) 구문은 옵셔널 Planet 을 생성하고 가져올 수 있다면 옵셔널 Planet 의 값을 somePlanet 에 설정
  • 이 경우 11 의 위치로 행성을 가져올 수 없으므로 대신에 else 구문이 실행
 

재귀 열거형

  • 재귀 열거형 (recursive enumeration) 은 열거형 케이스에 하나 이상의 연관값으로 열거형의 다른 인스턴스를 가지고 있는 열거형
  • 열거형 케이스가 재귀적임을 나타내기 위해 케이스 작성 전에 indirect 를 작성하여 컴파일러에게 필요한 간접 (indirection) 계층을 삽입하도록 지시

 

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
  • 간단한 산술 표현식을 저장하는 열거형의 예시
  • 열거형 시작 전에 indirect 를 작성하여 연관값을 가진 모든 열거형 케이스에 간접을 활성화
  • 이 열거형은 숫자, 2개의 표현식의 덧셈, 2개의 표현식의 곱셈의 3가지의 산술 표현식을 저장 가능
  • addition 과 multiplication 케이스는 산술 표현식인 연관값을 가지고 있고 이 연관값은 중첩 표현식을 가능하게 해줌
  • 예를 들어 (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))​
  • (5 + 4) * 2 에 대해 생성되는 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)
    }
}

print(evaluate(product))
// Prints "18"​
  • 산술 표현식을 판단하는 함수
  • 이 함수는 단순하게 관련된 값을 반환하여 숫자를 판단
  • 좌항의 식을 판단하고 우항의 식을 판단한 다음에 이를 더하거나 곱하여 덧셈 또는 곱셈을 판단
 

출처

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations

'iOS' 카테고리의 다른 글

Swift - Concurrency  (0) 2023.05.13
SwiftUI - Search Bar 구현  (0) 2023.05.11
Swift - Extensions  (0) 2023.03.05
Swift - Concurrency (동시성)  (0) 2023.02.26
Swift - Protocol(준수에 대한 검사, 상속, 옵셔널 프로토콜, 확장) (4/4)  (0) 2023.02.19
Comments