1. Swift : 열거형(Enumerations)
열거형
(Enumerations)
1) 이론적 정의
1. 하나의 주제로 연관된 데이터들이 멤버로 구성되어 있는 자료형 객체 (정의 참고 : 꼼꼼한 재은씨의 Swift - 문법편)
2. 연관된 값의 그룹(타입)에 대해 공통 타입을 정의한 뒤, 타입을 안전하게 함으로서 해당 값들을 사용 가능
2) 문법적 정의
enum name {
definition
}
* Reference. 열거형 값 네이밍 방법.
* PascalCase 사용 = 첫글자를 대문자로
* 각 case는 camelCase 사용 = 첫글자를 소문자로
3) Examples
* Example 1-1. 기본 정의 방법 + 네이밍 방법 적용
enum SomeEnumeration {
// case definition
}
* Example 1-2. case 네이밍 방법 적용
enum Starcraft {
case protoss
case zerg
case terran
case random
}
* 한 줄에 모두 표현해줄 수도 있다 !! 그러나 이렇게 사용할 경우에는 가독성을 고려하자 !
enum Starcraft {
case protoss, zerg, terran, random
}
* 열거형을 변수로 선언하고 값을 할당하는 방법들.
var choiceYourStart1 = Starcraft.protoss
choiceYourStart1 = .terran <-- Starcraft. 은 왜 생략되었을까? 앞에서 Starcraft. 이 들어간 것을 컴파일러가 기억하고 생략할 수 있게 해줌!!
var choiceYourStart2: Starcraft = .terran
* 열거형에서는 모든 case의 그룹이 되는 열거형의 name을 타입으로 선언할 수 있다.
그래서 위와 마찬가지로 앞에서 타입이 선언되었기 때문에 .terran 의 앞을 생략할 수 있다.
* Example 2. 열거형 값 매칭하기(Matching Enumeration Values)
Switch 문을 이용해서 열거형 내 일치하는 Case를 찾을 수 있다.
var choiceYourStart = Starcraft.zerg
switch choiceYourStart {
case .protoss:
print("Powerful")
case .terran:
print("Sociable")
case .zerg:
print("Much") <-- 일치하는 case
case .random:
print("Good luck")
}
var nameSCVOfTerran = Starcraft.terran
switch nameSCVOfTerran {
case .terran:
print("SCV") <-- 일치하는 case
default:
print("not SCV")
}
* 위에서 선언했던 열거형 enum Starcraft의 모든 case가 선언되면 default 값을 생략할 수 있으나,
그렇지 않은 경우에는 default 값을 선언해야 한다. 원하는 케이스만 찾고 나머지는 필요 없을 때 위의 스위치문처럼 활용.
* Example 3. 연관 값(Associated Values)
사용하는 시점에서 멤버에 보조 값을 설정할 수 있는 방법도 있다. 이렇게 설정된 값을 연관 값(Associated Values)이라 한다. 아래는 예시이다.
( 정의 참고 : 꼼꼼한 재은씨의 Swift - 문법편 )
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 4, 5, 7) <-- 변수 productBarcode 생성하고 Barcode의 upc 케이스 값을 사용하는 시점에 할당
productBarcode = .qrCode("ABC") <-- .qrCode(String)의 String 에 문자열 ABC 를 사용하는 시점에 값을 설정했다.
type(of: productBarcode) <-- Q. .qrCode("ABC")의 타입은 뭐지?? A. 연관된 값의 '그룹(Barcode)'이 타입이 된다.
print(productBarcode)
그렇다면, 변수가 아니라 let 을 사용하는 Case 라면?
열거형을 이용해 바로 상수에 할당하는 방법.
* Ref. var productBarcode = .qrCode("ABC") 상태.
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check): // 각 Int 값을 let + 이름으로 정의
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") // QQQQQQQQQQ 그냥 원래 값으로는 안되나? 왜 이렇게 쓰지? QQQQQQQQQQ
case .qrCode(let productCode): // 일치하는 case
print("QR code: \(productCode).")
}
productBarcode <-- qrCode("ABC")의 값을 갖는다.
각각 let을 써주기가 번거로울 때는 앞에 써주면 된다 !
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
* Example 4-1. 원시 값(Raw Value)
- 할당 가능한 타입 : Strings, Characters, or any of the Integer or Floating-point number types
- raw value 는 해당 Enumeration 내에서 반드시 고유한 값이어야 한다. 예시를 보면서 이해해 보자.
// Example 1.
앞에서 배웠던 열거형을 활용하는 방법이다.
enum ASCIIControlCharacter {
case tab
case lineFeed
case carriageReturn
}
ASCIIControlCharacter.tab
ASCIIControlCharacter.lineFeed
ASCIIControlCharacter.carriageReturn
// Example 2.
방금과는 다르게, 열거형의 이름 옆에 타입을 선언하는 차이가 생겼고, 각 case에도 Character 타입에 맞게 값이 들어가 있다.
enum ASCIIControlCharacter1: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r" \n 은 행바꿈, \t 는 '탭'을 의미한다.
}
ASCIIControlCharacter1.tab
ASCIIControlCharacter1.tab.rawValue <-- 결과 : " " ( 위에서 언급했듯, \t는 탭을 의미한다. lineFeed.rawValue는 줄 바꿈이 되겠죠? )
ASCIIControlCharacter1.lineFeed
ASCIIControlCharacter1.lineFeed.rawValue
아래의 결과를 확인해보자.
입력)
print("111\n행바꿈\t탭")
결과)
111
행바꿈 탭
// Example 3.
rawValue를 지정해주지 않은 상태에서는 Int 타입의 열거형의 경우 자동으로 0부터 1씩 증가하는 값이 할당된다.
enum Weekday: Int {
case sunday, monday, tuesday, wednesday, thursday, friday, saturday
}
Weekday.wednesday
Weekday.wednesday.rawValue <-- 'wednesday'의 경우 4번째 case 이니까 0,1,2,3 그러니까 3이 rawValue가 되겠다.
String의 경우에는 어떠할까?
enum WeekdayName: String {
case sunday, monday = "Mon", tuesday, wednesday, thursday, friday, saturday 1) 아까와는 다르게 monday에만 값을 지정했다.
}
WeekdayName.monday 2) 여기는 case 에 해당하는 monday 가 출력되고
WeekdayName.monday.rawValue 3) 여기는 monday 에 지정한 rawValue 값인 Mon 이 출력된다!
Q. 그럼 sunday의 rawValue 값은 무엇인가요?
A. 아래를 봅시다!
* Example 4-2. 암시적으로 할당된 원시 값(Implicitly Assigned Raw Value)
제목만 보면 이해가 잘 안될 수 있다. 예시를 보면서 익혀보자.
enum WeekdayAgain: Int {
case sunday, monday = 100, tuesday = 101, wednesday, thursday, friday, saturday
1) monday, tuesday에만 값이 할당된 상태이다. 그럼 궁금해질 수 있다. sunday에는 99가 아닐까?
2) 충분히 가능한 추론이지만 첫번째 case는 0부터 시작한다는 것을 잊지 말자.
3) wednesday는 그럼 1이 증가하는 102인가요???
4) 그렇다. wednesday는 102이 할당된다.
}
5) 그렇다면, 아래의 결과를 추론해보고 Xcode 에서 결과를 확인해보자 !
WeekdayAgain.sunday
WeekdayAgain.sunday.rawValue
WeekdayAgain.wednesday
WeekdayAgain.wednesday.rawValue
이번엔 Int 가 아닌 String의 예시다.
enum WeekdayNameAgain: String {
case sunday, monday = "mon", tuesday = "tue", wednesday, thursday, friday, saturday
1) String 의 경우에는 지정되지 않은 각 case 들은 case 의 이름 자체가 rawValue가 된다.
2) monday의 경우 mon 이 되겠지만, sunday의 경우 rawValue는 sunday가 된다... 쉽죠???
}
3) 아래의 결과를 추론해보고 Xcode 에서 결과를 확인해보자 !
WeekdayNameAgain.sunday
WeekdayNameAgain.sunday.rawValue
WeekdayNameAgain.wednesday
WeekdayNameAgain.wednesday.rawValue
* Example 4-3. 원시 값으로부터의 초기화(Initializing from a Raw Value)
원시 값을 이용해 초기화하는 것이 가능하다.
// Example 1.
enum PlanetIntRaw: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
Q. 음.. 궁금한게 생겼어요. mercury가 1이면 그 다음부터 1씩 증가할텐데, 그렇게 되면 earth가 3이 되잖아요. 근데 earth에 값을 안주고 mars에 값을 3 주면
어떻게 되나요???
A. mars에 3을 넣으면 반드시 고유한 값이어야 한다는 규칙에 어긋나서 'Raw Value for enum case is not uninque' 에러가 난다.
}
// 두 가지 받는 방법
PlanetIntRaw(rawValue: 2) <-- 원시 값을 이용한 초기화
PlanetIntRaw.venus <-- case의 이름을 이용한 초기화
// Example 2.
let possiblePlanet = PlanetIntRaw(rawValue: 7) <-- (= uranus)
let positionToFind = 11
if let somePlanet = PlanetIntRaw(rawValue: positionToFind) { // 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)") <-- enum PlanetIntRaw 에는 원시 값이 11이 되는 case는 가지고 있지 않다. 그래서 바로 여기로.
}
* Example 5. 열거형과 메소드
열거형은 클래스 혹은 구조체처럼 내부에 메소드를 정의할 수 있다.
enum Device: String {
case iPhone, iPad, tv, watch
func printType() {
switch self {
case .iPad, .iPhone, .tv:
print("device :", self)
case .watch:
print("apple Watch")
}
}
}
1) 열거형 내에 선언된 메소드를 사용해보자..
2) 값을 할당할 변수를 선언한다.
var myDevice: Device = .iPhone
3) myDevice에 지금 .iPhone 이라는 값이 담겼는데, 여기서 메소드에 넣어보자.
myDevice.printType()
4) Switch 문을 거쳐서, .iPhone에 해당하는 부분이 출력된다.
device : iPhone
* Example 6. 열거형의 중첩
열거형도 중첩하여 사용이 가능하다.
enum Wearable {
enum Weight : Int {
case light = 1
case heavy = 2
}
enum Armor: Int {
case lignt = 2
case heavy = 4
}
case helmet(weight : Weight, armor : Armor)
func attributes() -> (weight: Int, armor: Int) {
switch self {
case .helmet(let w, let a):
return (weight: w.rawValue * 2, armor: a.rawValue * 4)
}
}
}
let woodenHelmet = Wearable.helmet(weight: .light, armor: .heavy) <-- .light = 1 이고 .heavy = 4
woodenHelmet.attributes() <-- 값이 .helmet 일 경우, weight 의 원시 값을 2만큼 곱해주고 armor의 원시 값을 4만큼 곱해주는 Switch문을 사용하는 메소드.
지금까지 열거형에 대해서 알아보았습니다. 틀린 부분이 있으면 댓글로 지적해주시면 감사드리겠습니다.
Made by Inswag's Swift in FastCampus 7th iOS Development School