[ 이 게시물은 Swift.org를 참고하며 Swift를 공부하기 위해 작성하는 글 입니다. ]
열거형 (Enumeration)
열거형 (Enumeration)은 관련된 값을 그룹으로 만들기 위한 타입을 정의하고 코드에서 타입-세이프한 방법으로 동작하게 합니다.
열거형 구문 (Enumeration Syntax)
열거형은 enum 키워드로 선언하고 중괄호 안에 모든 정의를 선언합니다.
enum SomeEnumeration {
// enumeration definition goes here
}
아래 예제는 나침반 4개의 주요 포인트를 나타냅니다.
enum CompassPoint {
case north
case south
case east
case west
}
열거형 안에 정의된 값(north, south, east, wet)은 열거형 케이스이며 case 키워드를 사용하여 나타냅니다.
여러개의 케이스는 콤마로 구분하여 한줄로 표기할 수 있습니다.
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
각 열거형 정의는 새로운 타입으로 정의합니다. 열거형 타입에 단수 이름으로 지정할 수 있습니다.
var directionToHead = CompassPoint.west
directionToHead 는 초기화될 때 CompassPoint의 값 중 하나로 유추됩니다. 더 짧게 점 구문을 사용하여 CompassPoint 값을 설정할 수 있습니다.
directionToHead 의 타입은 이미 알고 있으므로 값을 설정할 때 타입 명시를 삭제할 수 있습니다. 따라서 명시적으로 타입화 된 열거형 값을 코드를 쉽게 읽을 수 있습니다.
스위치 구문의 열거형 값 일치(Matching Enumeration Values with a Switch Statement)
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")
default:
print("Not a safe place for humans")
}
// Prints "Watch out for penguins"
이 코드는 directonToHead의 값을 switch 문으로 돌리는데, .north일 경우 "Lots of planets have a north", .south와 같은 케이스일 경우 "Watch out for penguins"를 출력합니다.
모든 열거형 케이스에 대해 case를 제공할 수 없는 경우 default 케이스를 통해 정의할 수 있습니다.
열거형 케이스 반복 (Iterating over Enumeration Cases)
열거형 이름 뒤에 : CaseIterable 을 작성하여 열거형의 반복을 활성화시킬 수 있습니다. 이후, 열거형 타입에 allCases 프로퍼티를 통해 모든 케이스를 수집하고 방출할 수 있습니다. 아래는 예시입니다.
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"
아래 예시에서 Beverage 열거형의 모든 케이스를 포함하는 콜렉션에 접근하기 위해 for-in 루프와 Beverage.allCases를 사용합니다.
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
연관된 값 (Associated Values)
연관된 값을 저장하기 위해 Enum 열거형을 정의할 수 있고 이 값 타입은 필요에 따라 열거형의 각 케이스에 따라 달라질 수 있습니다.
예를 들어, 4개의 정수로 이루어진 UPC 바코드와 문자열을 저장하는 QR 코드 바코드, 두 타입의 Barcode를 정의하는 열거형은 아래와 같습니다.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
그러면 이러한 타입을 이용하면 아래와 같이 새로운 바코드를 생성할 수 있습니다.
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
같은 상품의 다른 바코드 타입은 아래와 같이 할당할 수 있습니다.
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
enum의 스위치 구문은 아래와 같이 사용할 수 있습니다. switch 케이스의 본문 내에서 사용하기 위해 상수 (let 접두사) 또는 변수 (var 접두사)로 값을 추출할 수 있습니다.
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."
암시적으로 할당된 원시값 (Implicitly Assigned Raw Values)
정수 또는 문자열 원시값이 저장된 열거형일 경우 각 케이스에 명시적으로 원시값을 가질 필요가 없고, 원시값을 설정하지 않으면 Swift는 자동적으로 값을 할당합니다.
예를 들어, 정수를 원시값으로 사용하면 각 케이스의 암시적 값은 이전 케이스에서 하나씩 증가됩니다. 그리고, 첫번째 케이스에 값 설정이 안되어 있으면 0 으로 설정합니다.
아래 열거형은 태양으로부터 각 행성의 순서를 정수값으로 나타내는 Planet 열거형입니다.
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
위의 예시에서 Planet.mercury 는 명시적을 원시값 1을 가지고 Planet.venus 는 암시적으로 원시값 2를 가지게 됩니다.
아래의 열거형은 각 방향의 이름을 문자열 원시값으로 나타내는 CompassPoint 입니다.
enum CompassPoint: String {
case north, south, east, west
}
위의 예시에서 CompassPoint.south는 "south"라는 암시적 원시값을 가지고 있습니다.
그리고, rawValue 프로퍼티를 사용하여 열거형 케이스의 원시값에 접근할 수 있습니다.
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
원시값으로 초기화 (Initializing from a Raw Value)
원시값 타입으로 열거형을 정의한다면 열거형은 원시값의 타입 (rawValue 라는 파라미터)을 사용하는 초기화를 자동으로 수신하고 열거형 케이스 또는 nil 을 반환합니다. 이 초기화를 이용하여 열거형의 새 인스턴스를 만들 수 있습니다.
아래 예시는 천왕성을 7 원시값으로 식별됩니다.
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
그러나 Int 값으로 모든 행성을 찾을 수는 없습니다. 이러한 점 때문에 원시값 초기화는 항상 옵셔널 열거형 케이스를 반환합니다. 위의 예시에서 possiblePlanet은 Planet? 타입입니다.
11 으로 행성을 찾는다면 원시값 초기화로부터 반환된 옵셔널 Planet 값은 nil 입니다.
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 의 원시값으로 행성을 찾기위해 옵셔널 바인딩을 사용합니다. if let somePlanet = Planet(rawValue: 11) 구문은 옵셔널 Planet을 생성하고 가져올 수 있다면 somePlanet에 값이 설정되고, 가져올 수 없다면 nil이 설정됩니다.
재귀 열거형 (Recursive Enumerations)
재귀 열거형 (recursive enumeration)은 열거형 케이스에 하나 이상의 연관된 열거형의 다른 인스턴스를 가지고 있는 열거형입니다. 열거형 케이스가 재귀적임을 나타내기 위해 케이스 작성 전에 indirect 를 작성하여 컴파일러에게 필요한 간접 (indirection) 계층을 삽입하도록 지시합니다.
예를 들어, 아래는 간단한 산술 표현식을 나타내는 열거형입니다.
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
열거형의 시작 전에 indirect 를 작성하여 연관된 값을 가진 모든 열거형 케이스에 간접을 활성화할 수 있습니다.
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
이 열거형은 숫자, 2개의 표현식의 덧셈, 2개의 표현식의 곱셉의 3가지의 산술 표현식을 저장할 수 있습니다. addition과 multiplication 케이스는 산술 표현식과 연관된 값을 가지고 있고 이 연관된 값은 중첩 표현식을 가능하게 해줍니다.
예를 들어 (5 + 4) * 2 표현식은 곱셈의 우항은 하나의 숫자를 가지고 있고 좌항은 다른 표현식을 가지고 있습니다. 데이터는 중첩되기 때문에 데이터를 저장하는 열거형도 중첩을 지원해야 합니다. 이것은 열거형은 재귀적이어야 한다는 의미입니다. 아래의 코드는 (5 + 4) * 2에 대해 생성되는 ArithmeticExpression 재귀 열거형을 나타냅니다.
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"
구조체와 클래스 (Structure and Classes)
구조체와 클래스에 대해 알아보겠습니다.
구조체와 클래스 비교
공통점
값을 저장하는 프로퍼티 정의
기능 제공을 위한 메소드 정의
서브 스크립트 구문을 사용하여 값에 접근을 제공하는 서브 스크립트 정의
초기화 상태를 설정하기 위한 초기화 정의
기본 구현을 넘어 기능적 확장을 위한 확장
특정 종류의 표준 기능을 제공하는 프로토콜 준수
클래스에는 존재하고 구조체에는 없는 기능
상속 사용 가능.
타입 캐스팅을 통해 런타임에 클래스 인스턴스의 타입을 확인 가능.
초기화 해제 구문 (Deinitializers)을 통한 클래스 인스턴스가 할당된 리소스 해제.
참조 카운팅은 하나 이상의 클래스 인스턴스 참조를 허락.
정의 구문 (Definition Syntax)
구조체와 클래스는 유사한 정의 구문을 가지고 있습니다. 구조체는 struct 키워드, 클래스는 class 키워드로 시작합니다. 둘다 중괄호 안에 정의가 위치합니다.
struct SomeStructure {
// structure definition goes here
}
class SomeClass {
// class definition goes here
}
아래는 구조체 정의와 클래스 정의의 예시입니다.
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
위의 예제는 픽셀기반의 화면 해상도를 설명하는 Resolution 이라는 새로운 구조체를 정의합니다. 이 구조체는 width와 height 두 개의 프로퍼티를 가지고 있습니다.
위 예시는 비디오 화면을 위한 특정 비디오 모드를 설명하는 VideoMode 라는 새로운 클래스를 정의합니다. 이 클래스는 4개의 프로퍼티를 가지고 있습니다.
구조체와 클래스 인스턴스 (Structure and Class Instances)
Resolution 구조체 정의와 VideoMode 클래스 정의는 오직 Resolution 또는 VideoMode 의 모양만 설명하고, 자체적인 해상도 또는 비디오 모드에 대해서는 설명하지 않습니다. 그렇게 하려면 구조체 또는 클래스의 인스턴스 생성이 필요합니다.
let someResolution = Resolution()
let someVideoMode = VideoMode()
구조체 타입에 대한 멤버별 초기화 구문 (Memberwise Initializers for Structure Types)
모든 구조체는 새로운 구조체 인스턴스의 멤버 프로퍼티를 초기화 할 때 사용할 수 있는 자동적으로 생성되는 멤버별 초기화 구문 (meberwise initializer) 을 가지고 있습니다. 새로운 인스턴스에 프로퍼티 초기값은 이름으로 멤버별 초기화에 전달 될 수 있습니다.
let vga = Resolution(width: 640, height: 480)
구조체와는 반대로 클래스 인스턴스는 멤버별 초기화를 가지지 않습니다.
구조체와 열거형은 값 타입 (Structures and Enumerations are value types)
값 타입 (value type) 은 변수 또는 상수에 할당될 때나 함수에 전달될 때 복사 되는 값인 타입입니다. 실제로 Swift에서 정수, 부동 소수점, 부울, 문자열, 배열 그리고 딕셔너리와 같은 기본 타입은 모두 값 타입이고 구조체로 구현되어 있습니다.
Swift에서 모든 구조체와 열거형은 값 타입입니다. 이는 생성한 구조체와 열거형 인스턴스와 프로퍼티로 포함된 모든 값 타입은 코드에서 전달될 때 복사된다는 의미입니다.
Resolution 구조체를 사용하는 아래 예제를 살펴봅시다.
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
이 예제는 hd 라는 상수를 선언하고 풀 HD 비디오 (1920 픽셀 너비와 1080 픽셀 높이)의 너비와 높이를 초기화하는 Resolution 인스턴스를 설정합니다.
그리고 Cinema 라는 변수를 선언하고 hd의 값을 설정하면, Resolution은 구조체이므로 기존 인스턴스의 복사본이 만들어지고 이 새 복사본에 cinema 가 할당됩니다. hd 와 cinema 는 같은 너비와 높이를 가지지만 2개는 완벽하게 다른 인스턴스 입니다.
다음으로 cinema 의 width 프로퍼티를 디지털 시네마 프로젝션에 사용되는 2K 표준 (2048 픽셀 너비)로 수정하면 cinema의 width 프로퍼티는 2048로 바뀌지만, 기존 hd 인스턴스의 width 프로퍼티는 1920 의 기존값을 그대로 가지고 있습니다.
print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"
print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

열거형에서도 같은 동작이 이루어집니다.
remeberedDirection 은 currentDirection 에 값이 할당될 때 실질적으로 복사본이 설정됩니다. 이후에 currentDirection 에 값을 변경해도 rememberedDirection 에 저장된 원래 값의 복사본에는 영향을 주지 않습니다.
enum CompassPoint {
case north, south, east, west
mutating func turnNorth() {
self = .north
}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"
클래스는 참조 타입 (Classes Are Reference Types)
값 타입과 반대로 참조 타입 (reference types) 은 변수 또는 상수에 할당될 때나 함수로 전달될 때 복사되지 않습니다. 복사본 대신에 같은 인스턴스에 대한 참조가 사용됩니다.
아래는 위에 정의된 VideoMode 를 사용하는 예시입니다.
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
이 예시는 tenEighty 라는 새로운 상수를 선언하고 VideoMode 클래스의 새로운 인스턴스를 참조하도록 설정합니다.
아래는 tenEighty 에 alsoTenEighty 라는 새로운 상수를 할당하고 alsoTenEighty 의 프레임 속도로 수정합니다.
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
클래스는 참조 타입이므로 TenEighty 와 alsoTenEighty 는 실질적으로 같은 VideoMode 인스턴스를 참조합니다. 그래서 아래 그림과 같이 하나의 인스턴스에 다른 2개의 이름을 가지고 있게 됩니다.

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"
References
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/
Documentation
docs.swift.org