Enum - 열거형

Created by
  • 한결
다른 프로그래밍 언어들과 비슷한 형태로 Swift도 열거형 타입(Enumeration Type)을 가진다. 열거 타입 내부에서 케이스에 따라 타입을 정의할 수 있고, 원시값을 담을 수도 있다.

열거형을 쓰는 이유는 여러가지가 있겠지만,

컴파일 단계에서 오류를 감지할 수 있다.
→ 런타임까지 해당 에러 탐지를 미루면 코드 작성에서 크리티컬한 상태를 안고가는 것과 비슷하다. (인간이 만드는 오류를 방지한다.)
타입에 대한 케이스 구분을 통해 코드에 명확성을 높일 수 있다. (코드 가독성을 높이는 효과)
→ 함수 파라미터에 열거형을 반영하여 내부 분기에 활요하면 그렇게 깔끔할 수가 없다. (내 생각)
구조체 / 클래스와 다르게 상속이 되는 개념이 아니기 때문에 인스턴스 생성을 신경쓰지 않아도 된다.
→ 반대로는 인스턴스화 할 수 없기 때문에, 데이터 하나하나의 유형(틀)으로 사용하기는 어렵다.
→ 구조체 멤버 변수 하나의 타입에는 지정하여 사용할 수 있겠다. (ex. User > 성별)

열거형 선언하고 이용하기

// 기본 case로 타입들을 구분하는 경우 enum Membership { case bronze case silver case platinum .. } // 케이스별로 원시값을 부여하는 경우 enum isPaid: Int { case free_trial = 0 case one_month = 1 case half_year = 2 .. }
case 키워드 기반으로 선언하고, 값을 부여하는 방식은 위와 같다.
열거형의 경우들과 값을 활용하려면 인스턴스의 변수를 부르는 것과 비슷하게 . 구분자로 불러오면 된다. (이렇기 때문에 컴파일 단계에서 오류를 잡을 수 있다.)
원시값이 부여된 경우는 rawValue로 할당된 값 자체에 접근할 수 있다.
Membership.bronze isPaid.one_month.rawValue // 1
enum User { case paid(grade: GradeEnum, signDate: Date) case noPaid }
원시값을 맵핑하는 것 이외에도 연관값(associated value)을 가진 형태로도 선언할 수 있다.
각 케이스별로 연관된 구체적인 정보를 반영해줄 수 있다.
enum APIURLs { static let aAPI = "https//..." enum Keys: String { case movie = "asdd-e.." } }
열거형 내부에서도 static 키워드를 이용해서 Typed Member를 정의할 수 있다. 사용 방법도 구조체나 클래스의 그것과 동일하다.
열거형 내부에서 또 다른 열거형을 정의하는 것도 가능하다. 비슷한 유형의 데이터셋 자체를 관리하는 용도로 좋을 것 같다.

열거형 다양하게 활용하기

연산형 멤버를 만들거나 private 제어자를 사용할 수도 있다.
enum ControlUserDefaults { private static let key = "k" static var userDefaults: String { get { return UserDefaults.standard.string(forKey: key) ?? "" } set { UserDefaults.standard.setValue(newValue, forKey: key) } } } func someFn() { ControlUserDefaults.userDefaults = "저장할 값" // set ControlUSerDefaults.userDefaults // get }
static 키워드를 이용해서 열거형의 멤버들을 제어할 수 있다면, 위와 같은 형태의 코드로 UserDefaults에 저장된 값을 제어하는 멤버를 만들어볼 수도 있다.
연산 멤버의 getter, setter를 잘 활용하면 함수나 메서드가 아닌데 마치 함수처럼 동작한다.
private 제어자로 UserDefaults에서 사용할 키 값을 열거형 안으로 숨길 수도 있다.
enum Styling { enum Color { static let primary: UIColor = .systemBlue } enum Font { static let sm: UIFont = .systemFont(ofSize: 13) } }
열거형 내부에 열거형들을 마치 멤버처럼 넣어서 비슷한 속성끼리 묶어볼 수도 있을 것 같다.
UI 작업을 할 때, 반복적으로 사용되는 코드를 이렇게 '멤버화' 해두면 확실히 코드 작성 속도가 빨라진다.
그렇지만, . 접근자를 이용해서 접근해야 하는 뎁스가 많이 길어지기 때문에 나는 그닥 선호하지 않는 것 같다. 나는 속성별로 열거형을 분리하거나 extension을 이용해서 기존 클래스의 역할을 확장해서 사용하는게 더 자연스러운 것 같다.
enum Options: String, CaseIterable { case A case B case C } Options.allCases[0].rawValue // 마치 배열처럼 사용할 수 있다. // static var allCases: [Options] { get }
CaseIterable 프로토콜을 체택하면, .allCases 멤버가 내부에 구현된다.
get 만 할 수 있는 연산형 멤버이기 때문에 값을 추가하거나 덮어쓰는건 불가하다.
간단한 가로 스택 버튼 그룹을 만들 때, 버튼의 타이틀이나 고유값을 제어할 때 아주 유용하게 사용될 수 있겠다.

Frozen

열거형은 기본적으로 선언한 개발자에 의해 케이스가 더 추가될 수도 있고 그렇지 않을 수도 있다. 이건 앱 입장에서 불확실성을 높이는게 될 수 있는데, 이런 것을 예상하고 안정성을 높이기 위해 frozen 키워드를 활용한다.
애플의 내장 프레임워크, 외부 라이브러리에 작성된 열거형 자료를 switch 구문과 함께 활용할 경우 @unknown default에 대한 처리가 필요하다. (컴파일 에러가 나지는 않지만 이후 런타임단에서 에러가 날 수 있다.)
이는 frozen 상태가 아닌 열거형에 이후 더 다른 케이스가 생성될 수 있음을 보장할 수 없기 때문이다.
열거형에 @frozen 어트리뷰트를 붙이면, 이후 해당 열거형에 다른 케이스가 추가될 가능성이 없다는 것을 보장한다.
@frozen public enum Optional<Wrapped> { case none case some(Wrapped) }