2014년 6월 11일 수요일

Swift - 열거형(Enumerations)

열거형(Enumerations)이란 C의 enum과 비슷한 것으로, 특정한 이름을 나열하고 이 이름을 값 대신 쓸 수 있도록 하는 기능이다. 스위프트(Swift)의 enum 도 비슷한 용도를 위해 사용한다.

이 열거형이란 일종의 상수를 정의하는 용도로 사용된다. 특정 타입에 대한 값을 정의하고, 이 타입의 값 중 한가지를 골라서 변수에 할당할 수 있다.

가장 단순하게 아래와 같은 식으로 열거형 정의가 가능하다. LifeType이라는 enum을 정의하는 건데 '생물' 이라는 타입을 정의하는 의도다.
enum LifeType {
    case None
    case Human
    case Animal
    case Bug
}
생물의 종류는 없음(모름), 사람, 동물, 벌레 세 가지로 정의해 놓았다는 의미로 보자.

참고로 위 코드를 콤마를 이용해 case를 하나만 사용하는 축약된 방식으로 바꿀 수 있다.
enum LifeType {
    case None, Human, Animal, Bug
}
enum 내부에 정의된 이름은 값(데이터) 대신 이용 할 수 있다. 다만 네임스페이스가 해당 타입에 소속된다는 점이 다르다.
var type = LifeType.Human
type을 사람(Human)으로 정의한다는 의도의 코드이다.

다만 한번 특정 타입의 enum 으로 변수가 만들어지면 다른 값으로 바꾸는건 타입 이름을 생략해도 가능하다.
type = .Animal
이제 type은 사람이 아닌 동물을 지칭하게 되었다.

여기까지 보면 C의 방식과 모양은 약간 다르지만 거의 동일하게 이용이 가능하다.

enum으로 정의된 타입 내부에 case로 정의된 이름들은 모두 값으로 사용되므로 논리식으로 활용된다.
type = .Animal
type == .Animal            // true
type == LifeType.Animal    // true
그리고 switch - case 문에서 편하게 활용이 가능하다.
switch type {
case .Human:
    println("Human")
case .Animal:
    println("Animal")
case .Bug:
    println("Bug")
default:
    println("Unknown")
}
역시 같은 타입 내에서 비교이기 때문에 enum 타입 이름을 생략한 것을 알 수 있다.

switch - case 문은 논리 제어문(Conditional Statements) 글을 참고하자.

값이 할당된 enum - Raw Values

C의 enum 처럼 각 값에 특정 데이터를 할당 할 수도 있다. 아마도 이 형식이 가장 일반적으로 많이 쓰여왔던 enum 예제같다. 그런데 스위프트에선 타입이 정의되어야 가능한 기능이다.
enum SomeItemType: Int {
    case None = 0, Text, Audio, Movie, Picture
}
Int(정수형) 타입에 한해서 한 이름에 값을 설정하면 그 다음부터 나열되는 이름은 순차적으로 값이 증가되어서 할당된다. 그래서 위의 경우 Text는 1, Audio는 2 이런 식으로 할당된다. 만약 타입이 String 등이라면 자동증가 할당 기능은 못 쓰겠지만 그 나름대로 유용할거라 생각된다.

이렇게 할당된 값은 직접 쓰는 값이 아니고 Raw Value 라고 불린다. 번역하면 날값 이라고 해야되나 -_-;; 이 Raw Value 를 얻기 위해서 toRaw() 라는 메소드를 활용 할 수 있다.
var itemType = SomeItemType.None
println(SomeItemType.None.toRaw())   // 0
이 외에 이 Raw Value 를 이용해 enum의 값을 할당하는 스위프트의 독특한 기능도 있다. enum 타입의 fromRaw() 메소드를 이용한다. enum 타입 생성자 중 rawValue 를 사용 할 수 있다.
let itemType2 = SomeItemType(rawValue: 2)
itemType2 == SomeItemType.Audio      // true
위에서 2번은 Audio에 자동으로 할당된 값이다. 그래서 .Audio 라는 값이 자동으로 할당된다. 참 신선한 기능이다. :-)

참고로 enum 타입을 만들 때 Raw Value 타입을 Int로 정의해 놓고 실제 Raw Value를 명시하지 않으면 0 부터 자동으로 배당이 된다.

연관값(Associated Values)

선언 당시에 Raw Value를 할당하는 것과는 다르게 사용자 마음대로 특정 데이터를 값에다 할당하는 독특한 기능도 있다. 이 때 넣는 값을 연관값(Associated Value)이라고 한다.
enum LifeType {
    case None
    case Human(String)    // 특정 이름의 사람
    case Animal(Bool)     // 동물인데 포유류이면 true
    case Bug(Int, Bool)   // 다리 몇 개(Int)를 가진 벌래인데 갑각류이면 true
}
위 예제에서 각 enum 값의 연관값의 타입만 선언만 해 놓은걸 볼 수 있다. 그리고 Bug 처럼 2개 이상의 연관값을 선언하는 것도 가능하다. 오른쪽에 주석문은 어떤 의미로 쓸 건지 메모해 둔 것이다

이 enum을 실제로 사용하는 방식은 아래와 같다.
// Chris Jean 이라는 사람
var type1 = LifeType.Human("Chris Jean")

// 포유류 동물
var type2 = LifeType.Animal(true)

// 다리가 6개이고 갑각류인 벌레
var type3 = LifeType.Bug(6, true)
마치 enum의 각 값을 별도의 데이터 타입처럼 사용하는 것 처럼 보인다.

enum의 값을 비교할 때는 switch - case 문이 굉장히 요긴하게 쓰인다. 특히 이 연관값 기능을 이용 할 때 최고의 효율이 나타난다.
switch type1 {
case .Human(let name):
    println("This is Human named \(name)")
case .Animal(let isMammal):
    println("This is " + (isMammal ? "mammal" : "non-mammal") + " animal")
case .Bug(let numLegs, let hasShell):
    println("This is bug has \(numLegs) legs with " + (hasShell ? "shell" : "no shell"))
default:
    println("Unknown")
}
위 코드가 실행되면 "This is Human named Chris Jean" 이라고 콘솔에 찍힐 것이다.

연관값 기능을 쓰면 case 에 해당하는 값이 발견되면 해당 enum 값에 소속된 연관값을 특정 이름을 메겨서 받을 수 있다. 그래서 위와 같이 활용이 가능해진다.

그나저나 연관값 기능은 막강한데 도데체 이걸 어디다 어떻게 써 먹어야 할까라고 고민하는 나는 좀 낡은 프로그래머가 같다. 워낙 C 같은 단순한 언어를 오래 썼고 그래서 사고방식도 단순하게 굳어졌나보다. ㅠㅠ

[관련글] Swift - 논리 제어문(Conditional Statements)

[돌아가기] 스위프트(Swift) 가이드

댓글 2개 :

강철 :

let itemType2 = SomeItemType.fromRaw(2)

다음과 같이 변경된거 같습니다.

let itemType2 = SomeItemType(fromRaw:2)

Seorenn :

지적 감사합니다. 바뀐 문법을 일일이 다 찾아서 고친다는게 쉬운 일은 아니네요. :-)