KWiOS
KWiOS0101
KWiOS
  • 분류 전체보기 (108)
    • Algorithm (41)
      • 이코테 (14)
      • 이코테 문제풀이 (21)
      • 프로그래머스 (6)
    • CS (1)
      • 모두를 위한 컴퓨터 과학(CS50 2019) (0)
    • iOS (15)
    • Swift (36)
      • Swift문법 (32)
      • 기타 (4)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 6

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
KWiOS

KWiOS0101

Swift/Swift문법

Swift문법 - Enumeration

2023. 3. 5. 00:24

Enumeration Types

열거형 타입이란 연관된 상수들을 하나의 이름의 자료형으로 묶는것을 말한다. 열거형은 독립적인 자료형이며 열거형을 사용함으로써 코드의 가독성과 안정성이 높아진다.

기본 문법 📝
enum TypeName {
    case caseName
    case caseName, caseName

열거형의 기본 문법을 보면 열거형 이름은 어퍼카멜케이스로 선언해야하고

enum TypeName

케이스의 이름은 로우어카멜 케이스로 선언하며 중복되면 안된다.

case caseName

여러 케이스를 한번에 선언하려면 콤마로 나열하여 한줄에 선언할 수 있다.

case caseName, caseName

이렇게 기본적인 규칙을 지켜 열거형을 선언해보면 아래와 같이 선언 할 수 있다.

enum Alignment {
    case left
    case center
    case right
}

다음은 열거형을 사용하는 방법을 알아보자. 기본적으로 변수에 열거형을 저장할 수 있다. 저장하기전에 Alignment.center 의 출력값을 보면 케이스 이름으로 값이 출력되는 것을 볼수있다.

Alignment.center //center

이제 새로운 변수에 저장하는법을 알아보자.

var testAlignment  = Alignment.center

testAlignment 변수에 Alignment.center 를 저장시켰다. 여기서 저장된 값을 center가 아닌 right로 다시 저장하고 싶다면

testAlignment = .right

이렇게 right를 저장시켜주면 된다. 여기서 처음 center를 저장한 방법과의 차이점은 열거형 이름을 생략한것인데, 맨처음 열거형 이름을 생략하지 않고 사용했으므로 컴파일러는 자동으로 열거형 이름을 인식하고 있기 때문에 생략해도 문제가 없다. 하지만 주의할점은 .을 생략하게되면 컴파일러가 변수나 상수로 인식할 수 있다.

.right

위와같이 .right 만 있다면 열거형인데 이름이 생략되어있구나라고 인식하면 된다.


Raw Values

다음으로는 열거형의 case에 또 다른 값을 저장할 수 있는 원시값에 대해서 알아보자.

원시값이란 열거형의 케이스는 모두 독립적인 값이지만 내부에 또 다른 값을 저장할 수 있는것이 원시값이다. 그러나 원시값은 열거형을 선언할 때 무조건 원시값을 저장해야하는것은 아니므로 선택적으로 사용하면 된다.

원시값은 한번 선언하게되면 바꿀수 없고 원시값을 저장하는 부분은 생략할 수 있으며 자료형마다 정해진 규칙에 따라 기본값이 정해진다.

기본 문법 📝
enum TypeName: RawValueType {
    case caseName: Value
// Int, String, Character 타입만 사용 가능 
enum TypeName: Int {
    case caseName: Value

enum TypeName: String {
    case caseName: Value

enum TypeName: Character {
    case caseName: Value

먼저 원시값의 기본 문법은 열거형 이름에 RawValueType을 선언해주어야 한다. 여기서 중요한점은 RawValueType에는 Int, String, Character 타입만 사용 가능하다.

case caseName: Value // value 생략 가능

그리고 케이스에 벨류값을 저장해도되고 안해도 된다.

먼저 Int 타입을 사용할때 RawValue를 어떻게 사용하는지 알아보자.

enum Numbers: Int {
    case zero
    case one
    case two
}

Numbers 열거형을 선언해주고 RawValueType을 Int타입으로 선언해주었다.

Numbers.zero.rawValue // 0
Numbers.one.rawValue // 1
Numbers.two.rawValue // 2

열거형을 선언했을때 원시값을 지정해주지 않았다면 0부터 1씩 증가하면서 기본값이 정해진다.

enum Numbers: Int {
    case zero
    case one = 10
    case two
}

이렇게 one에 원시값 10을 직접 할당해주면

Numbers.zero.rawValue // 0
Numbers.one.rawValue // 10
Numbers.two.rawValue // 11

two의 값은 이전에 할당된 case의 원시값을 기준으로 +1 증가한 값이 저장된다.

다음으로는 열거형에서 사용되는 특별한 생성자가 있는데

enum Numbers: Int {
    case zero
    case one = 10
    case two
}

Numbers(rawValue: 10) // one
Numbers(rawValue: 11) // two
Numbers(rawValue: 12) // nil

rawValue의 값을 받는 생성자를 사용하면 해당 원시값과 매칭되는 case가 출력된다. 하지만 매칭되지 않는 원시값을 사용할 경우 nil이 리턴된다. 이로인해서 알수있는것은 nil이 리턴되기 때문에 생성자의 리턴형은 옵셔널인것을 알 수 있다.

다음은 String 타입을 사용할때 RawValue를 어떻게 사용하는지 알아보자.

먼저 요일을 열거형으로 만들었다. 이렇게 요일같이 한정적인 경우에 열거형으로 만든다.

enum Weekday: String {
    case sunday
    case monday = "Mon"
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}
Weekday.sunday.rawValue // "sunday"

String 타입일때는 원시값을 저장하지 않으면 case 이름 그대로 출력된다.

case monday = "Mon"

Weekday.monday.rawValue // "Mon"

String타입도 원시값을 지정해주면 원시값으로 저장된 값을 출력할 수 있다.

마지막으로 Character 타입을 사용할때 RawValue를 어떻게 사용하는지 알아보자.

enum ASCII: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

Character 타입일때는 반드시 원시값을 선언해주어야 한다. 원시값을 선언해주지 않으면 컴파일 에러가 발생한다.


Associated Values

다음으로는 연관값에 대해 알아보자.

연관값은 열거형 case에 원시값 대신에 연관된 값을 저장하는것을 말한다. 연관값을 사용하면 원시값의 한계를 해결할 수 있다.

원시값의 한계란 무엇이냐면

  1. 모든 case는 동일한 타입이여야한다.
  2. case당 하나의 값만 저장 가능하다.
  3. 원시값 문자열에 숫자가 포함되어 있으면 숫자를 사용할 때 따로 추출해서 사용해야한다.
기본 문법 📝
enum TypeName {
    case caseName(Type)
    case caseName(Type, Type, ...)
}

먼저 연관값의 기본 문법을 보면 케이스이름 다음 튜플로 연관값을 저장한다.

enum Fruit: String {
    case zero = "apple, 200"
    case one = "banana, 300"
    case two = "melon, 400"
}

위 코드는 과일의 이름과 가격을 문자열로 저장하고있는데 가격만 사용하고싶을때는 숫자를 따로 추출해서 사용해야하는 번거로움이 있다. 이때 연관값을 적용해서 사용하면 된다.

enum Fruit {
    case zero(name: String, price: Int) // named
    case one(String, Int) // unnamed
    case two(String, Int) // unnamed
}

연관값은 상황에 맞게 named, unnamed 튜플로 사용가능하다.
또한 연관값을 확인한 후에는 주로 Switch문을 사용한다.

var newFruit = Fruit.zero(name: "apple", price: 500)

switch newFruit {
case .zero(name: "apple", price: 500):
   print("연관값 사용 가능 ")
case .zero(name: "apple", _):
   print("와일드카드 패턴 사용 가능")
case .zero:
   print("연관값 생략가능")
case .zero(let name, let price):
   print("블록 내부에서 연관값 사용할때 상수 변수 사용가능")
case let .zero(name, price):
   print("모든 연관값 동일한 형태로 바인딩")
default:
   break
}

Enum Case Pattern

다음으로는 Enum Case Pattern에 대해 알아보자.

Enum Case Pattern은 연관값을 가진 열거형의 case를 매칭시킬때 사용하며 if, for, while, switch, guard문에서 모두 사용 가능하다.

기본 문법 📝 
case Enum.case(let name):
case Enum.case(var name):

각 케이스의 연관값은 상수나 변수로 바인딩 가능하다.

기본 문법 📝 
case let Enum.case(name):
case var Enum.case(name):

만약 바인딩 방식이 동일하다면 let,var 키워드를 case뒤에 써준다.

case .case(let name):
case .case(var name):
case let .case(name):
case var .case(name):

열거형 이름은 추론이 가능하기 때문에 생략 가능하다.

if

Enum Case Pattern을 if문에서 사용하는법을 알아보자.

enum Transportation {
    case bus(number: Int)
    case taxi(company: String, number: String)
    case subway(line: Int, express: Bool)
}

var commute = Transportation.subway(line: 2, express: false)

// 2호선 지하철
if case let .subway(2, express) = commute {
    if express {
        // 급행인 경우
    } else {
        // 급행이 아닌 경우
    }
}

// 급행 지하철
if case .subway(_, true) = commute {
    print("express")

먼저 Transportation이라는 열거형을 선언해주고

enum Transportation {
    case bus(number: Int)
    case taxi(company: String, number: String)
    case subway(line: Int, express: Bool)
}

commute변수의 기본값 2, false를 할당해주었다.

var commute = Transportation.subway(line: 2, express: false)

그 다음 if문에서 첫번째 값은 2로 매칭시키고 두번째 값은 express를 상수로 바인딩 해주었다. 그 다음 할당연산자를 사용해서 매칭시킬값을 commute변수로 저장했다. 아래 코드에서 if블록이 실행될때가 언제냐면 .subway 케이스에서 첫번째 연관값이 2로 저장되어있을때 실행되며 express는 if블록 내부에서 사용된다. 쉽게말해서 아래 코드는 2호선인지 먼저 확인하고 급행여부에 따라서분기되는 코드이다.

if case let .subway(2, express) = commute {
    if express {
        // 급행인 경우
    } else {
        // 급행이 아닌 경우
    }
}

급행 지하철인지만 확인하고싶을때는 첫번째 연관값을 와일드 패턴으로 무시하고 두번째 값을 true와 매칭시키는데 아래 코드에서는 바인딩 시키지 않았기때문에 case 뒤에 let,var 키워드를 쓰지 않는다.

// 급행 지하철
if case .subway(_, true) = commute {
    print("express")
}

for

다음으로는 Enum Case Pattern을 for문으로 사용하는법을 알아보자

let list = [
    Transportation.subway(line: 1, express: true),
    Transportation.bus(number: 5438),
    Transportation.subway(line: 2, express: false),
    Transportation.taxi(company: "naver", number: "7777")
]

// 지하철
for case let .subway(n, _) in list {
    print("subway \(n)") // "subway 1" , "subway 2"
}

// 급행 지하철
for case let .subway(n, true) in list {
    print("subway \(n)") "subway 1"
}

// 지하철 중 2호선만
// where절로 조건 검색 가능
for case let .subway(n, _) in list where n == 2 {
    print("subway \(n)") // "subway 2"
}

새로운 배열에 열거형을 저장시켜주고

let list = [
    Transportation.subway(line: 1, express: true),
    Transportation.bus(number: 5438),
    Transportation.subway(line: 2, express: false),
    Transportation.taxi(company: "naver", number: "7777")
]

for문을 사용할때는 배열에 저장된 요소가 반복상수로 전달되기 때문에 if문처럼 할당연산자로 저장하는 부분은 필요가 없다.

// 지하철
for case let .subway(n, _) in list {
    print("subway \(n)") // "subway 1" , "subway 2"
}

// 급행 지하철
for case let .subway(n, true) in list {
    print("subway \(n)") "subway 1"
}

// 지하철 중 2호선만
// where절로 조건 검색 가능
for case let .subway(n, _) in list where n == 2 {
    print("subway \(n)") // "subway 2"
}

마지막으로 Enum Case Pattern을 Switch문으로 사용하는법을 알아보자

commute = Transportation.bus(number: 5438)

switch commute {
case .bus(let number):
    print(number)
case .taxi(let company, _):
    print(company)
    // 바인딩이 필요없으면 와일드 카드로 생략 가능
case let .subway(line, express):
    print(line, express)
    // 바인딩 방식이 동일하다면 let,var case 뒤로 빼서 사용
}

CaseIterable

열거형에서 마지막으로 CaseIterable을 알아보도록 하자.

CaseIterable은 모든 case를 열거할 수 있게 도와주는 프로토콜로 모든 case를 컬렉션, 즉 배열로 리턴해준다.

예제코드를 보면 Weekday 열거형을 선언해준 다음

enum Weekday: Int {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}
let rnd = Int.random(in: 0...6)

Weekday(rawValue: ran) 

코드를 몇번 실행하해보면 rnd 상수를 이용하여 랜덤한 요일 선택된다. 여기서 random함수의 범위는 열거형에 원시값을 저장하지 않으면 0부터 +1씩 증가하기 때문에 0~6까지를 범위로 지정할 수 있다.
다음으로Weekday(rawValue: ran) 열거형 인스턴스를 생성하고 실행해보면 원시값에 따라 리턴되는 값이 바뀌는걸 볼 수 있는데 여기서 sunday에 초기값을 100으로 주면 범위가 100부터 시작하기 때문에 에러가 발생한다.

let rnd = Int.random(in: 0...6)
Weekday(rawValue: ran) 

이제 CaseIterable을 이용하여 코드를 작성해보자.

enum Weekday: Int, CaseIterable {
    case sunday = 10
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}
let rnd = Int.random(in: 0...Weekday.allCases.count)

// let rnd = Int.random(in: 0...6)일때만 사용가능
Weekday(rawValue: ran) 

Weekday.allCases.randomElement()

// 모든 case 열거하기
for i in Weekday.allCases {
    print(i)
}

전체 코드에서 달라진점은

enum Weekday: Int, CaseIterable {

CaseIterable 프로토콜이 추가되었다.

let rnd = Int.random(in: 0...6)
rnd = Int.random(in: 0...Weekday.allCases.count)

CaseIterable을 사용하게되면 어퍼바운드가 6이 아니라 Weekday.allCases.count 로 바뀌어야한다.
이렇게 코드를 바꾸면 원시값이 있어도 범위로 인한 에러는 발생하지 않는다.

Weekday(rawValue: ran) 
Weekday.allCases.randomElement()

호출할때도 .allCases.randomElement()를 사용해서 호출해야한다.

// 모든 case 열거하기
for i in Weekday.allCases {
    print(i)
}

열거형의 모든 case를 열거하려면 for문을 사용해서 열거할 수 있다.

'Swift > Swift문법' 카테고리의 다른 글

Swift문법 - Properties  (0) 2023.03.19
Swift문법 - Struct and Class  (0) 2023.03.12
Swift문법 - Collection  (0) 2023.02.26
Swift문법 - String Options  (0) 2023.02.19
Swift문법 - String and Characters  (0) 2023.02.12
    'Swift/Swift문법' 카테고리의 다른 글
    • Swift문법 - Properties
    • Swift문법 - Struct and Class
    • Swift문법 - Collection
    • Swift문법 - String Options
    KWiOS
    KWiOS

    티스토리툴바