Programming/Swift

1. Swift : 열거형(Enumerations)

INSWAG 2018. 6. 15. 23:40


열거형

(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(IntIntIntInt)

    case qrCode(String)

}


var productBarcode = Barcode.upc(8457)                      <-- 변수 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 주면

         어떻게 되나요???

    Amars에 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