티스토리 뷰

Swift

열거형(Enumeration) 정복하기 - 2장

swallow.iOS 2022. 5. 30. 21:22

안녕하세요! Swallow입니다!

이번 포스팅은

⭐️ 열거형(Enumeration) ⭐️

관한 글입니다!

이전 포스팅에서는

열거형의 기본적인 부분을 다뤘다면

이번 포스팅에서는 원시값(Raw Value)과 연관값(Associated Value) 내용에 대해

이야기해볼게요!

항상 어떠한 조언, 충고 환영입니다!

시작하기에 앞서 이 글은

앨런 Swift 문법 마스터 스쿨 강의와 야곰님의 스위프트 프로그래밍 책

공부하고 참고하여 작성하였습니다!

그럼 시작하겠습니다!

 

 

이번 글에서는

열거형의

원시값과 연관값에 관하여

작성해보도록 하겠습니다!

 

우선 첫 번째로

원시값(Raw Value)이란?

열거형의 케이스들에 숫자 또는 문자열과 매칭 시켜서 자유롭게 활용하는 개념입니다!

쉽게 말씀드리면

각각의 케이스에 숫자 또는 문자열을

매칭 시켜서 값을 가지는 개념이라고 생각하시면 됩니다!

바로 원시값 선언 방법을 알아보겠습니다!

enum Chicken: String {
    case bbq
    case bhc
    case puradak
    case kyochon
}

위의 예시가 열거형이 원시값(Raw Value)을 가지고 있는 경우입니다!

원시값(Raw Value)

열거형 정의를 할 때 바로 옆에 또 다른 타입을 넣어주면 됩니다!

이제 원시값(Raw Value)의 정의 방법은 알았고,

그러면 원시값을 설정을 했으니

무슨 일이 일어날까요?

enum Chicken: String {
    case bbq = "bbq"
    case bhc = "bhc"
    case puradak = "puradak"
    case kyochon = "kyochon"
    case gcova = "지코바"
}

원시값을 설정을 해주면

각각의 케이스에 자동으로 값이 매칭이 되는데,

위의 예시에서는 String타입으로 원시값을 설정했으니

case 이름 자체가 문자열로 매칭이 됩니다! 

굳이 위의 예시처럼 직접 할당하지 않아도

자동으로 case 이름 자체가 문자열로 매칭이 되니

기억해두시기 바랍니다!

 

그런데

case 이름 자체를 원시값으로 할당하기 싫으시면

case gcova = "지코바"처럼

직접적으로 원시값을 할당해 주시면 됩니다!

 

그런데 여기서 중요한 부분이

원시값(Raw Value)으로 정의 가능한 타입은

Hashable 해야 합니다!

 

저희가 자주 사용하는

Int, String, Double 등 기본적인 자료형

모두 Hashable 합니다!

Hashable에 관한 내용은 나중에 다루겠습니다!

 

이번엔 Int타입 원시값을 알아보겠습니다!

enum Alphabet: Int {
    case a  // 0
    case b  // 1
    case c  // 2
    case d  // 3
}

위의 예시는 Int타입으로 원시값을 설정을 했습니다!

Int타입으로 원시값을 설정을 하면

첫 번째 케이스부터 자동으로

0부터 매칭이 됩니다!

다음 케이스에는 이전 케이스의

원시값 + 1 한 값이 들어갑니다!

 

이 부분도 위와 마찬가지로

나는 다른 숫자를 원시값으로 줄 거야! 하시면

직접적으로 원시값을 넣어주시면 됩니다!

 

그런데

일반적으로는 중간 케이스부터 원시값을 건드리지 않고

첫 번째 케이스부터 원시값을 변경합니다!

 

만일 case c의 원시값을 5로 매칭을 했다면

case a와 case b의 원시값은

0과 1이 매칭이 되고

case d는 이전 케이스의 원시값 + 1 한 값이 매칭이 되어

원시값 6이 매칭이 됩니다!

 

그럼 이제 원시값도 설정을 했으니

열거형을 생성해 볼까요?

var anything = Alphabet(rawValue: 0)  // 인스턴스 생성 -> 옵셔널로 리턴
var bValue = Alphabet.b.rawValue  // 접근연산자를 사용해 case b의 원시값 자체에 접근

var anything777 = Chicken(rawValue: "bhc")  // 인스턴스 생성 -> 옵셔널로 리턴, 일반적으로 문자열 방식으로는 잘 사용 안함
var bhcValue = Chicken.bhc.rawValue  // 접근연산자를 사용해 case bhc의 원시값 자체에 접근

anything변수에 Alphabet 열거형 내부에 존재하는

원시값이 0인 인스턴스를 생성했습니다!

즉, 원시값이 0이 매칭이 된 인스턴스를 생성을 했으니

case a가 생성이 되었습니다!

그리고, bValue변수에

Alphabet 열거형 내부에 존재하는

case b의 rawValue에 접근을 하였습니다!

그러면 case b의 rawValue에 접근을 하였으니

1에 접근을 하는 행위입니다!

아래의 anything777과 bhcValue도 같은 경우입니다!

 

 

여기서 알아두시면 좋은 포인트가 존재합니다!

위의 예시를 Xcode에서..

Alphabet(rawValue: Int)를 통해 인스턴스를 생성할 때

만일 원시값의 범위를 벗어난 값을 파라미터로 주었다면

과연 어떤 일이 생길까요? 🧐

 

rawValue 함수

다행히 anything변수의 타입을 알아보면

옵셔널 타입입니다!

즉, 원시값의 범위를 벗어난 값을 파라미터로 주어도

에러가 발생하지 않고, nil로 리턴을 해준다!

라는 의미입니다!

기억해두시면 매우 좋습니다!

 

위와 같은 이유 때문에

열거형에서 rawValue를 통해 인스턴스를 생성 후,

사용을 하기 위해선

이전에 포스팅한 옵셔널 추출을 사용하여

옵셔널을 벗겨낸 후에 사용을 해야 합니다!

 

이전 포스팅에서

열거형은 일반적으로 switch문에서

많은 처리를 한다고 말씀드렸습니다!

왜 switch문으로 많이 처리를 하냐면

간단하게 설명을 드리면

열거형은 한정된 case로 이루어져 있는 타입이고,

switch문은 표현식에 대한 분기 처리에

최적화되어있기 때문에

활용하기에 가장 적합합니다!

 

원시값을 가진 열거형도 switch문 처리가 가능하겠죠?

바로 보여드리겠습니다!

원시값 switch문

위의 예시에서는 원시값 자체를 가지고

switch문을 활용해 보았습니다!

 

 

자! 그러면

원시값에 대해서는 이 정도로 끝내고

이제 두 번째 내용인

연관값(Associated Value)에 관하여

알아보겠습니다!

 

연관값(Associated Value)이란?

각 케이스에 구체적인 추가 정보를 저장하는 개념입니다!

즉, 케이스가 카테고리의 역할로 변한다고 생각하시면 쉽습니다!

역시 글로 보는 것보다 확실한 예시가 필요하겠죠?

연관값 정의하는 방법을 알아보겠습니다!

enum Course {
    case appetizer(String)  // 각각의 케이스의 괄호안에 튜플형태로 타입 명시하기
    case main(meat: String, g: Int)
    case dessert(fruit: String)
}

위의 예시처럼

각각의 case 옆에 튜플 형태로 원하는 타입을 명시해주면 됩니다!

꼭! 튜플 형태로만 명시해주는 것이 아니라,

튜플 형태로 사용이 가능하다 라는걸 알아두시면 좋겠습니다!

 

자! 그러면 연관값을 왜 쓰느냐?🧐

궁금해하실 분들이 계실 거 같아요!

 

그 이유는!

원시값의 경우 정의를 하면

개발자가 정의한 형태로만 존재합니다!

즉, 원시값을 Int 타입으로 정의를 하면

Int타입으로만 값을 가진다 라는 의미입니다!

그리고 각각의 케이스마다

개발자가 정의한

한 가지의 값만 가질 수밖에 없습니다!

 

하지만 연관값의 경우

위의 예시처럼

정의 단계에서 튜플 형태로 형식을 정해놓으면

열거형을 생성을 할 때

정의해 놓은 형식에 맞춰서

알맞은 타입을 넣어 하나의 케이스에 서로 다른

연관값을 저장할 수 있습니다!

 

그럼, 이제 연관값을 가진 열거형을 생성해 볼까요?

연관값 예시

위의 사진처럼 연관값을 가진 열거형을 생성을 할 때

개발자가 정의해 놓은 case와 연관값을 함께 알려주고

정의해 놓은 타입에 맞춰 열거형을 생성해 주면 됩니다!

enum Course {
    case appetizer(String)
    case main(meat: String, g: Int)
    case dessert(fruit: String)
}

var birthday: Course = Course.main(meat: "beef", g: 200)
var niceDay: Course = Course.main(meat: "pork", g: 150)

var present: Course = Course.dessert(fruit: "peach")
var happy: Course = Course.dessert(fruit: "melon")

 

위에서 설명드린 대로

하나의 케이스에 서로 다른 연관값을 넣어서

열거형을 생성해보았습니다!

 

위에서도 말씀드렸다시피

열거형은 일반적으로 switch문에서

많은 처리를 한다고 말씀드렸습니다!

그런데, 꼭!!

switch문에서만 처리가 가능한 게 아니라

switch문으로

많이 처리한다는 뜻입니다!!

 

그럼, 연관값을 가진

열거형의 switch문 처리 보도록 하겠습니다!

연관값 switch문

delicious변수에 case main의 연관값을 설정하고,

switch문을 이용해 처리를 해보았습니다!

그리고 바인딩의 예시도 보여드렸습니다

case let .main(a, b) 이 부분이

어떤 의미를 할까요?

 

바인딩이기 때문에

let a = 첫 번째 연관값

let b = 두 번째 연관값

이렇게 할당이 된다고 생각하시면 됩니다!

즉, let a = meat의 값

let b = g의 값

쉽죠?

 

위에서 이러한 처리를 했을 때

과연 어떻게 출력이 되었을까요?

연관값 switch문 출력

네, 맞습니다!

생각하신 대로

두 번째 case를 만나서 위와 같이 출력이 됩니다!

 

마지막으로 원시값과 연관값의 차이점

관해서 말씀드리겠습니다!

 

원시값은 값의 저장이 언제 되느냐?

열거형의 선언 시점에 값의 저장이 이루어집니다!

즉, 원시값을 설정을 하고

열거형의 완전한 선언이 완료되었을 때

값이 저장된다는 의미입니다!

그리고 원시값은 선언 시에 정의를 하는 개념이기에

case에 들어간 값의 변경이 당연히 불가능합니다!

 

연관값의 값의 저장은 언제 되느냐?

원시값과 다르게 선언 시에 값이 저장이 되는 게 아니라,

연관값 같은 경우 선언 시에 구체적인 정보를 추가하는 게 아닌

구체적인 정보의 타입을 설정하기 때문에

값의 저장 시점은 원시값과 다르고

연관값은 인스턴스의 생성 시에 값의 저장이 이루어집니다!

즉, 위의 예시들처럼

구체적인 값을 넣어주고 인스턴스를 생성하는 시점에

값의 저장이 이루어진다라고 생각하시면 됩니다!

 

그리고 당연하게도

하나의 열거형에서 원시값과 연관값을 동시에 사용은 불가능합니다!

 

이번 포스팅에서는

원시값(Raw Value)과 연관값(Associated Value)에

관하여 알아보았습니다!

다음 포스팅에서는

⭐️ 열거형 case 패턴과 ⭐️

이야기해보겠습니다!

 

감사합니다!

 

피드백 언제나 환영입니다!

 

 

댓글