티스토리 뷰

Swift

Swift [열거형(Enumerations)]

이도형 2023. 2. 12. 23:17

Index

1. 열거형의 정의 및 조건문

2. 열거형의 원시값(RawValues)과 연관값(associated Values)

3. ⭐️옵셔널 타입에 대한 정확한 이해⭐️

4. 열거형의 활용 

5. 열거형의 배열 타입의 반복문 출력 예제

6. 마무리

 

1. 열거형의 정의 및 조건문

1-1 열거형의 정의

열거형(enumerations): 연관된 case들하나의 이름으로 묶은 자료형(타입)

1. 열거형은 언제 사용하는가?

한정된 사례 안에서 정의할 수 있을때, 정해놓은 값만 입력받고 싶을때

2. 열거형의 장점은?

코드의 가독성과 안정성이 높아져 명확한 분기처리가 가능함

enum은 value타입으로 stack영역에 저장되기때문에 성능 향상에 도움

 

1-2 열거형의 조건문

열거형은 한정된 사례내에서 정의하기에, switch문으로 분기처리가 가능하다(default문이 없이도 완벽 구현 가능).

각 case는 .문법으로 접근한다.(밑에 나올 원시값(RawValues), 연관값(Assosicated Values)도 마찬가지)

 

if문과 switch문의 일반적인 열거형 분기처리 예제

//1. 열거형의 기본 정의

enum Weekday{
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
    case sunday
}

enum CompassPoint{
    case north, south, east, west
}


var today: Weekday = Weekday.monday
today = .friday

//1-2 열거형의 조건문 -> 자유자재 구현 연습하기⭐️

if today == .saturday{
    print("토요일이다!")
} else if today == .sunday{
    print("일요일이다!")
} else { print("평일...") } //"평일...\n"


today = .sunday //값 변경

switch today{
case .monday, .tuesday,.wednesday,.thursday,.friday:
    print("평일...")
case .saturday:
    print("토요일이다!")
case .sunday:
    print("일요일이다!") //"일요일이다!\n"
}

//if문으로 특정 경우의 처리도 가능하다.
if Weekday.saturday == today{
    print("토요일이다!")
}

 

2. 열거형의 원시값(RawValues)과 연관값(associated Values)

 

  2- 1 열거형의 원시값(RawValues)

열거형의 원시값은 매칭되는 기본값(Int, String)을 정해 열거형을 좀 더 쉽게 활용가능하다

여러가지 형태로 원시값 정의 가능(Hashable한 Int, String,Double... 가능하나 But 대부분의 원시값 타입은 Int로 정의)

암시적으로 할당된 원시값 - 원시값을 따로 설정하지 않으면, swift가 자동으로 값 할당.

맨위 case의 rawValue가 0을 초기값으로 가지고, 각 case의 원시값은 이전 case보다 1씩 크다.

 

원시값을 입력하여 case에 접근하고, 점문법으로 원시값 알아내는 예제

//원시값(RawValue) 있는 열거형 선언하기
enum Alignment: Int { //원시값이 있는 열거형은 타입 선언이 필수적임. 대부분 Int로 원시값 선언
    case left
    case center
    case right
}

var direction: Alignment = .left

direction.rawValue //0

enum Alignment1: String {
    case left = "L"
    case center = "C"
    case right = "R"
}

//⭐️원시값으로 case접근하고, 점문법으로 원시값 접근하기
let align = Alignment1(rawValue: "L") //left //원시값 입력으로 case접근 가능 (리턴타입은 옵셔널)
let leftValue = Alignment1.left.rawValue // 0 //점문법으로 원시값 접근 가능

아래는 원시값이 있는 열거형의 추가예제들이다. 연습용으로 한번 풀어보며 if let바인딩 부분을 주의해서 보자.

// rock,scissors,paper를 case로 가지는 RpsGame 열거형을 원시값(0,1,2)을 가지는 형태로 정의해보기

enum RpsGame: Int {
    case rock = 0
    case scissors = 1
    case paper = 2
}

var myRps = RpsGame(rawValue: 0)

myRps = RpsGame(rawValue: 2)
let number = Int.random(in: 1...100) % 3

print(myRps!) //paper  //원시값이 있는 열거형은 옵셔널 타입으로 리턴된다.(인스턴스에 입력한 원시값이 없을 수도 있다!)
print(RpsGame(rawValue: number)!) //rock //출력은 하지만 String은 아니다

//if let바인딩으로 안전하게 추출
if let x = RpsGame(rawValue: number) {
    print(x) //rock
}

enum Planet: Int{
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus,neptune
}

let planet = Planet(rawValue: 5)

if let x = planet?.rawValue{
    //지금은 이해 못하겠지만, 위의 RpsGame(rawValue: number)와 동일한 접근 방식이다.
    //포스팅을 쭉보고 다시 보면 RpsGame(rawValue: number)는 ~패턴, 
    // planet?.rawValue는 ~패턴이라고 답할 수 있어야 한다.🧐
    print(x) // 5
}

 

2-2 연관값(associated Values)

 원시값의 한계점 - 모든 case가 동일 타입(Int면 Int, String이면 String)으로만 RawValue를 가질 수 있었고 그 값도 1개만  가질 수 있었음. 연관값은 이러한 단점을 보완한다

 

 연관값 구체적인 추가정보 저장을 위해 사용한다.

 각 케이스별로 상이한 특징이 있고, 그것을 저장, 활용할 필요가 있을 때,

 개별케이스마다 저장할 형식을 따로 정의(자료형에 제한이 없음 / 튜플의 형태)한다.

 하나의 케이스에 서로 다른 연관값을 저장할 수 있다 ==> 선언시점이 아니라, 새로운 열거형 값을 생성할때 저장한다.

 

원시값과 연관값의 차이는?

1.자료형 선언 방식이 다르다(원시값은 단일 타입 정의, 연관값은 각case별 소괄호로 정의)

2. 값의 저장 시점이 다르다(원시값은 선언시점, 연관값은 열거형으로 값 생성시)

3. 하나의 열거형에 원시값과 연관값이 공존할 수 없다.

 

//연관값(associated Value)있는 열거형 선언하기
enum Computer{
    case cpu(core: Int, ghz: Double)
    case ram(Int, String)
    case hardDisk(gb: Int)
}

//해당 case가 상이한 특징일때 주로 사용한다.
let myChips1 = Computer.cpu(core: 4, ghz: 2.5)
let myChips2 = Computer.ram(32, "SRAM")
let myCHips3 = Computer.hardDisk(gb: 256)


//⭐️연관값의 switch문 처리⭐️
var chip = Computer.cpu(core: 8, ghz: 3.0)

switch chip{
case .cpu(core: 8, ghz: 2.0):
    print("8core, 2.0 ghz")
case .cpu(core: 8, ghz: 3.0):
    print("8core, 3.0ghz")
default:
    print("몰랑")
}

3. ⭐️옵셔널 타입에 대한 정확한 이해⭐️

 

열거형의 연관값에 대해 배웠기 때문에, 이제는 옵셔널 타입을 완전히 이해할 수 있게 되었다.

앞서 옵셔널은 nil일수도 있는 한층 더 감싸고(Wrapped)있는 타입이라고 하였다.

정확히 말하면 옵셔널은 2개의 case로 구성되어있다. case1은 .some(연관값) 이고 case2는 .none이다.  

옵셔널은 열거형(enum) + 제네릭(generic)으로 구현된 형태다.

제네릭(generic)은 뒤에 포스팅 하겠지만, 간단히 말하면 다른 여러 타입(Int, String...)이 가능한 타입을 말한다. 그래서 옵셔널은 어떻게 구현되었냐?고 묻는다면, 연관값이 저장된 some 케이스와 none케이스로 구성된 제네릭 열거형이라라고 답할 수 있다.  옵셔널의 내부적 설계를 코드로 표현하면

enum Optional<Wrapped>{
    case some(Wrapped) //값이 있음
    case none //값이없음
}

//<Wrapped>가 생소해 보일 수 있으나, wrapped 말그대로 "나 감싸져 있어"를 의미한다.

// 옵셔널하면 ?가 가장 먼저 생각나겠지만 정식문법은 Optional<Int>였다.
var a: Int?
var b: Optional<Int>

❗그렇다. a와 b는 완전히 동일한 타입이다.(type? == Optional<type>)

case인 some에는 연관값으로 제네릭 타입(어떤 타입이든 가능)인 Wrapped이 담겨있다.

여기서 우리는 옵셔널을 선언할때, 그 데이터가 (연관값인) Wrapped타입에 해당되면 some에 해당되고, 값이 없는 상태라면 none에 해당된다는 것을 알 수 있다.

nil을 통해 값의 유무를 판별할 수 있던것도 옵셔널이 열거형으로 정의되었기 때문에 가능했던 것이다. 아래 코드를 보면

 

var num: Int? = 5
switch num{ //num은 Optional<Int> 타입
case .some(let a):
    print(a)
case .none:
    print("nil")
}

 .none은 값이 없다는 것을 명시적인 열거형으로 표현한 형태이며, 동일한 nil키워드가 일반적으로 사용된다

따라서 (Optional).none과 nil은 동일한 표현이다.

 

4. 열거형의 활용

지금까지 배운 개념들을 통해 열거형을 활용해보자. 

열거형은 개념 자체는 어렵지 않고 쉬운축에 속한다. 하지만 열거형을 실제로 활용하려면 코드에 익숙해져 있지 않으면 자연스러운 구현으로 이어질 수 없다.  많이 따라 쳐보고, 익숙해져야 이해도도 높아진다고 생각해 지금까지 배운 개념에 따른 예제들을 모아놓았다.(그냥 내가 연습하려고 모아 놓은 것들ㅋㅋㅋㅋ)

아래와 유사한 예제들을 직접 만들어 열거형에 익숙해지는 시간을 가져도 좋을 것 같다. 앞의 예시로 들었던 코드도 있고 패턴 매칭방식이 들어간 새로운 코드들도 등장한다. 

 

1. 일반적인 열거형 타입 정의후 switch문으로 분기처리 후, if문으로 특정경우도 처리하기

enum LoginProvider {
    case apple
    case facebook
    case google
}

let userLogin: LoginProvider = .google

switch userLogin{
case .apple: print("apple 로그인")
case .facebook: print("facebook 로그인")
case .google: print("google 로그인")
}

if LoginProvider.google == userLogin{
    print("google 로그인")
}

 

2. 원시값이 String인 열거형 타입 정의후 상수 2개를 선언하여 출력하기

(첫번째 상수는 원시값을 입력해 case에 접근하고 if let바인딩을 통해 출력)

(두번째 상수는 점 문법으로 원시값에 접근하여 원시값을 출력)

enum Direction: String{
case east = "E"
case west = "W"
case south = "S"
case north = "N"
}

let me = Direction(rawValue: "W") //원시값을 입력해 case에 접근하고
let you = Direction.east.rawValue // 점 문법으로 원시값에 접근하여 

//print(me!) 강제 추출방식도 가능하나,
if let x = me{ //안전하게 if let바인딩을 통해 출력
    print(x) //west
}

//위와 동일 코드
//if let x = Direction(rawValue: "W"){
//    print(x)
//}

print(you) //E

3. 🔥연관값이 있는 열거형 타입 정의후, switch문으로 분기처리를 하되, case에 값 바인딩 패턴과 와일드 카드 패턴 적용하여 default문을 작성하지 않고도, switch문을 완성시키기

enum MainDish{
    case pasta(taste: String)
    case pizza(dough: String, topping: String)
    case coke(Int)
}

var myDish = MainDish.pizza(dough: "치즈", topping: "불고기")

//switch문 열거형 case 패턴 매칭
switch myDish{
case .pasta(taste: let a): print("\(a)맛 파스타입니다.")
case .pizza(dough: "치즈", topping: "포테이토"): print("치즈 포테이토 피자")
case .pizza(dough: "치즈", topping: let a): print("치즈 \(a)피자 입니다.")
case .pizza(dough: _, topping: _): print("기본 피자입니다.")
case .coke(_): print("콜라는 무제한입니다.")
}

//치즈 불고기피자 입니다.

//if문 열거형 case 패턴 매칭(주로 특정 케이스만 다룰때)

myDish = MainDish.pizza(dough: "아무거나", topping: "고구마") //myDish변경
if case MainDish.pizza(dough: _, topping: let a) = myDish{
    print("치즈 \(a) 피자입니다.")
}
//치즈 고구마 피자입니다.

myDish = MainDish.coke(1) //myDish변경
if case MainDish.coke(0...) = myDish{
    print("오늘 콜라는 무제한입니다.")
}
//오늘 콜라는 무제한입니다.

 

 

4. 열거형 case 패턴과 옵셔널 패턴 예제

 

앞서 옵셔널은 .some과 .none의 케이스로 구성되어 있다고 했다. 이 개념을 떠올린 채로 아래의 코드들을 보자.

enum SomeEnum {
    case left
    case right
}

// 타입을 다시 옵셔널 열거형으로 선언 ⭐️

let x: SomeEnum? = .left

// SomeEnum?의 의미: 옵셔널 열거형
// - 값이 있는 경우 .some ===> 내부에 다시 열거형 .left /.right
// - 값이 없는 경우 .none ===> nil

 

열거형 case 패턴은 Enum타입을 각 case와 매칭시켜 데이터를 사용한 것이라면

옵셔널 패턴옵셔널 타입을 이용해 값을 매칭시켜 데이터를 사용하는 것이라 볼 수 있다.

 옵셔널 열거형 타입은 훨씬 간편하다. (.some(let x) -> x?)

코드를 보고 위 문장을 다시 읽으면 쉽게 이해될 것이고 옵셔널열거형에 대한 이해도도 높아질 것이다.

아래 4개의 switch문은 모두 동일하다. 4번째 switch문이 우리가 간편히 사용하는 형태이고,

정식 문법으로 거슬러 올라가면 맨위의 형태가 나온다.(1,2,3번째는 옵셔널 열거형의 이해를 위한 코드)

 

switch x{
case .some(let value):
    switch value {
    case .left: print("왼쪽으로")
    case .right: print("오른쪽으로")
    }
case .none: print("계속 go")
}

switch x{
case Optional.some(let value):
    switch value {
    case .left: print("왼쪽으로")
    case .right:print("오른쪽으로")
    }
case Optional.none:
    print("계속 go")
}

switch x{
case Optional.some(SomeEnum.left): print("왼쪽으로")
case Optional.some(SomeEnum.right): print("오른쪽으로")
case Optional.none: print("계속 go")
}

//switch문에서 옵셔널 열거형 타입을 사용할때, 벗기지 않아도 내부값 접근가능⭐️
	
switch x {     // 제일 편한 형태. 예전에는 x! 라고 써줘야 했음 (스위치문에서 옵셔널 타입도 ok)
case .left:
    print("왼쪽으로 돌기")
case .right:
    print("오른쪽으로 돌기")
case nil:
    print("계속 전진")
}

 

열거형 case 패턴과 옵셔널 패턴의 분기처리 추가예제

let num: Int? = 7

//1.열거형 case패턴(열거형 내부의 연관값 사용)

switch num{
case .some(let x): print(x) //7
case .none: print("nil")
}

//2. 옵셔널 패턴(.some(let x) -> let x?) => .some을 ?로 대체해 편하다
switch num{
case let x?: print(x) //7
case nil: print("nil")
}

let str: String? = "AB"

//1. 열거형 case패턴으로 분기 처리
switch str{
case .some(let a): print(a) //AB
case .none: print("nil")
}

//2. 옵셔널 패턴으로 분기 처리
switch str{
case let a?: print(a) //AB 
case nil: print("nil")
}

if case .some(let a) = str{
    print(a) //AB
}

(자동 생성의 간편함)

5. 연관값이 있는 열거형 타입과 옵셔널 타입 요소 배열을 반복문을 통해 구현해보자

-> 튜플과 바인딩 방식에 더 익숙해지게 된다.

//연관값이 있는 열거형타입 배열의 반복문 구현하는법
enum Computer{
    case cpu(core: Int, ghz: Double)
    case ram(Int, String)
    case hardDisk(gb: Int)
}


let chiplists: [Computer] = [
    .cpu(core: 4, ghz: 3.0),
    .cpu(core: 8, ghz: 3.5),
    .ram(16, "SRAM"),
    .ram(32, "DRAM"),
    .cpu(core: 8, ghz: 3.5),
    .hardDisk(gb: 500),
    .hardDisk(gb: 256)
]

//모든 경우에 대한 for문
for chip in chiplists {
    print(chip)
}
//cpu(core: 4, ghz: 3.0)
//cpu(core: 8, ghz: 3.5)
//ram(16, "SRAM")
//ram(32, "DRAM")
//cpu(core: 8, ghz: 3.5)
//hardDisk(gb: 500)
//hardDisk(gb: 256)

//바인딩을 활용한 특정 case에 대한 for문
for case let Computer.cpu(core: c, ghz: h) in chiplists{
    print("CPU칩: \(c)코어, \(h)헤르츠")
}
//CPU칩: 4코어, 3.0헤르츠
//CPU칩: 8코어, 3.5헤르츠
//CPU칩: 8코어, 3.5헤르츠

for case .ram(let a, let b) in chiplists{
    print("\(a)ram, \(b)")
}
//16ram, SRAM
//32ram, DRAM

옵셔널 타입을 원소로 가지는 배열 arrays

//옵셔널 타입요소 배열을 for문을 통해 구현하는 법
// 옵셔널 타입을 포함하는 배열에서 반복문을 사용하는 경우, 옵셔널 패턴을 사용하면 편리함
let arrays: [Int?] = [nil, 2, 3, nil, 5]

// 1) 열거형 케이스 패턴
for case .some(let number) in arrays {
    print("Found a \(number)")
}

// 2) 옵셔널 패턴
//옵셔널 바인딩을 통해 순회함으로써 nil일때는 반복문이 실행되지 않는다.
var count = 0
for case let number? in arrays {
    print("Found a \(number)")
    count += 1
}
print("반복 횟수: \(count)")

 

6.마무리

쉬우니 익숙해지기만 하면 된다. 열거형은 한정된 사례정의와 분기처리에 적합한 타입이었다.

Index마다 기억에 남아야 될건 열거형이 어떻게 선언되고, 원시값 연관값이 무엇인지, 그리고 옵셔널타입이 무엇과 무엇으로 어떻게 감싸져있었는지를 이해하는 것이다.

그리고 이렇게 배운 개념들을 통해 열거형의 활용의 예제들을 쉽게 해결할 수 있다면 PASS

 

 

출처

Inflearn Allen님의 강의 노트

Swift 공식문서

야곰님의 스위프트 프로그래밍 3판

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함