iOS/Swift

Swift No.8

ParkSeongGeun 2024. 1. 9. 20:28

No.8

Type

  • Basic Type
    • 스위프트에 내장되어 있는 기본 타입
    • Int, Double, String, Bool …
  • Custom Type
    • 개발자가 마음대로 만들어서 쓸 수 있는 타입 (사용자 정의 타입)
    • Enum, Class, Struct

열거형(Enumeration)

  • 타입 자체를 한정된 사례(case) 안에서 정의할 수 있는 타입
    • e.g : 동서남북, 일주일, 가위바위보 … etc
  • 연관된 상수(케이스)들을 하나의 이름으로 묶은 자료형
  • enum Weekday { case monday case tuesday case wednesday case thursday case friday case saturday case sunday } enum CompassPoint { case north, south, east, west }
  • 열거형을 사용하면 미ㅣ 정의해둔 타입의 케이스에서 벗어날 수 없으므로 코드의 가독성과 안정성이 높아짐
    • 명확한 분기 처리 가능
    • enum School { case elimentary case middle case high case university } var school: School = School.elimentary
  • 열거형은 타입이다!
  • var today: Weekday = Weekday.monday // .monday 도 가능 today = .friday // == Weekday.friday
  • 열거형은 항상 switch문으로 분기처리 가능
    • 당연히 if문도 가능하지만 일반적으로 switch문으로 한다.
    • switch today { case .monday: print("오늘은 월요일입니다.") case .tuesday: print("오늘은 화요일입니다.") case .wednesday: print("오늘은 수요일입니다.") case .thursday: print("오늘은 목요일입니다.") case .friday: print("오늘은 금요일입니다.") case .saturday: print("오늘은 토요일입니다.") case .sunday: print("오늘은 일요일입니다.") }

열거형의 원시값(Raw Value)과 연관값(Associated Value)

원시값

  • 열거형을 정의할 때 옆에 타입을 입력해주는 것(= 숫자를 매칭시켜줌)
  • 여러가지 형태로 원시값을 정의 가능 (Hashable한 - Int / String / Character / Double 등 가능)
  • 숫자 또는 문자열과 매칭시켜 자유롭게 활용 가능 (숫자 ←→ 열거형 타입)
enum Alignment: Int {
        case left = 0 // 안 써줘도 자동으로 처음부터 0, 1, 2로 matching 시켜줌
        case center = 1 // 만약 3을 할당하면 다음 right은 4. 즉, 이전 값 + 1해준 것.
        case right 
}

// 원시값 입력안하면 0, 1, 2  이렇게 자동으로 저장됨 (정수의 경우 숫자가 하나씩 증가)
// 1(설정시), 2, 3
// 0, 2(설정시), 3
let align = Alignment(rawValue: 0)    // 인스턴스 생성시 - 옵셔널타입으로 리턴 (실패가능)

if let i = align {
    print(i)
}

let erralign = Alignment(rawValue: 4)

enum Alignment1: String {
        case left // "left" 
        case right // "right"
        case center // "center"
}

func checkNilValue(a: Alignment?) -> Bool {
    guard let i = a else {
        print("nil value!")
        return false
    }
    print("\(i) is in parameter")
    return true
}

checkNilValue(a: align)
checkNilValue(a: erralign)
enum RpsGame: Int {
    case rock
    case paper
    case scissors
}

// 실제 앱을 만들때는
RpsGame(rawValue: 0)!
RpsGame(rawValue: 1)
RpsGame(rawValue: 2)

// 논리적으로 nil이 될 수 없다면
let number = Int.random(in: 0...100) % 3    // 3을 조금 더 멋지게 처리할 수 있는 것은 고급내용에서 다룸
var value = RpsGame(rawValue: number)! // RpsGame Type
print(RpsGame(rawValue: number)!)      // 출력은 하지만 문자열은 아님에 주의 , RpsGame 타입이다.

연관값

  • 구체적인 추가정보를 저장하기 위해 사용
    • 각 케이스별로 상이한 특징이 있고, 그것을 저장 / 활용할 필요가 있을 때
    • 개별케이스마다 저장할 형식을 따로 정의(자료형에 제한이 없음 / 튜플의 형태)
    • 어떤 정보가 바뀔 때마다 무한대로 케이스를 만들고 싶을 때 사용하는 개념
    • 하나의 케이스에 서로다른 연관값을 저장할 수 있음 → 선언시점이 아니라, 새로운 열거형 값을 생성할때 저장
enum Profile {
        case size(height: Int, weight: Int)
        case birth(year: Int, month: Int, day: Int)
        case from(country: String, local: String)
}

let psg = Profile.size(height: 181, weight: 76)
let foden = Profile.birth(year: 2000, month: 8, day: 1)

var student = Profile.size(height: 185, weight: 75)

switch student {
case .size(height: 185, weight: 65):
        print("1")
case .size(height: 185, weight: 70):
        print("2")
case .size(height: 185, weight: 75):
        print("3")
case .size(height: 185, weight: 80):
        print("4")
case .size(height: 185, weight: 85):
        print("5")
default:
        print("체급 설정 불가")
}
// 값을 바인딩해서 사용도 가능
switch student {
case let .size(h, w):
        print("\(h) -> \(w)")
default:
        print("hello zzz")
}

원시값과 연관값의 차이

  1. 자료형 선언 방식 : 선언 위치가 다름
  2. 선언 형식
    • 원시값 : enum 자료형 옆에다가 타입을 선언하고, 자동 or 수동 설정
    • 연관값 : 튜플의 형태로 형식 제한 없음
  3. 값의 저장 시점
    • 원시값 : 선언시점
    • 연관값 : 새로운 열거형 값을 생성할 떄
  4. 서로 배타적 : 하나의 열거형에서 원시값과 연관값을 함께 사용하는 것은 불가능 함.
구분 원시값 연관값
사용 목적 열거형 타입의 각 케이스에 정수 또는 문자열을 매칭시켜, 타입을 생성하거나 다룰때 조금 더 편하게 사용 열거형 타입의 각 케이스의 카테고리에는 해당하지만, 보다 구체적인 정보를 저장해서 사용하려고 할 때
선언 방법 선언 타입이 제한 - Int, String을 주로 사용  
(원칙적으로 Hashable 프로토콜을 준수하는 타입 모두 가능) 선언 타입에 제한없이 자유롭게 정의 가능  
(연관된 정보를 추가적 저장 개념이기 때문에)    
     
값의 저장 열거형의 선언 시점  
(선언시에 case마다 값이 매칭) 하나의 케이스에 서로 다른 값들을 저장가능  
주의점 하나의 열거형에서 원시값, 연관값을 동시에 사용하는 것은 불가능  

옵셔널 타입에 대한 정확한 이해

  • 옵셔널(Optional) 타입의 내부는 열거형, 열거형의 연관갓으로 값이 저장
enum Optional<Wrapped> {
        case some(Wrapped)
        case none
}

// 옵셔널의 구현은, 제너릭 열거형으로 선언하고 some의 케이스에 연관값을 저장한 것
// Int?
// -> .some(연관값) // 구체적인 정보에 Int값이 들어 있는 것
    // e.g : 7, 8 ... 
// -> .none // 구체적인 정보에 값이 없음
    // e.g : 값이 없음을 나타내는 키워드 nil

var num: Int? = 7

// 열거형(한정된 사례)
//Optional.some(7)
//Optional.none

  • 우리가 기존 if let i = num { print(i) … } 와 같이 사용하던 것은 내부적으로 다음과 같이 설계가 되어 있기 때문에 사용 가능하다.
  • //내부적으로 switch num { case .some(let a): // 열거형 case 패턴을 활용해서, 내부 연관값을 꺼냄 print(a) case .none: print("nil") } //switch num { //case Optional.some(let a): // print(a) //case Optional.none: // print("nil") //}
  • .none, nil은 동일
    • none은 명시적으로 나타낸 것, nil은 값이 없다는 걸 나타내는 키워드

열거형과 switch문

  • 열거형에 대한 구체적인 처리는 스위치(switch)문과 함께 쓸때, 사용성이 높아짐
  • 열거형은 한정된 사례(case)로 만든 타입이고, 스위치문은 표현식에 대한 분기처리에 최적화 되어 있기 때문에, 활용에 적합
  • enum LoginProvider: String { case email case facebook case google } // 열거형은 한정된 사례로 만든 타입이고, // 스위치문은 표현식에 대한 분기처리에 최적화 switch userLogin { // 3가지로 분기처리 case .email: print("이메일 로그인") case .facebook: print("페이스북 로그인") case .google: print("구글 로그인") } // 물론 특정한 경우도 존재 (if문으로 처리)

Optional Enum (옵셔널 열거형)

  • 열거형 자체에 물음표를 붙인 것
  • 열거형에 (연관값이 없고), 옵셔널 열거형의 경우
    • switch문의 편의성 (열거형 case 패턴) - Enumeration Case Pattern
  • 옵셔널 자체가 열거형으로 선언되어 있고, 옵셔널 열거형으로 선언하는 경우 열거형을 감싸는 열거형의 형태가 됨 (이중 잠금 장치)
  • 열거형은 한정된 사례(case)로 만든 타입이고, 스위치문은 표현식에 대한 분기처리에 최적화 되어 있기 때문에, 활용에 적합 + 스위치문은 옵셔널 쉽게 사용 기능 제공

→ 단 열거형에 연관값이 없는 경우에만 편의적 기능을 사용할 수 있다.

// 열거형의 선언

enum SomeEnum {
    case left
    case right
}

// 타입을 다시 <옵셔널 열거형>으로 선언 ⭐️
let x: SomeEnum? = .left

/**==================================================
 [SomeEnum?의 의미] 옵셔널 열거형

 - 값이 있는 경우 .some ===> 내부에 다시 열거형 .left /.right (이중 보완)
 - 값이 없는 경우 .none ===> nil
=====================================================**/

// 원칙
switch x {
case .some(let value):      // Optional.some(let value) = Optional.some(SomeEnum.left)  ⭐️
    switch value { // 첫 번째 switch문에서 한 번 벗기고, 두 번째 switch문에서 또 벗겨줌.
    case .left:
        print("왼쪽으로 돌기")
    case .right:
        print("오른쪽으로 돌기")
    }
case .none:
    print("계속 전진")
}

// 편의적 기능 제공 ⭐️
switch x {
case .some(.left): // == Optional.some(SomeEnum.left):
    print("왼쪽으로 돌기")
case .some(.right):
    print("오른쪽으로 돌기")
case nil:
    print("계속 전진")
}

열거형에 연관값이 있는 경우

  1. 연관값(Associated Values)이 있는 경우와 switch문 (열거형 case 패턴) - Enumeration Case Pattern
  • 연관값이 있는 열거형의 활용 (열거형 case 패턴) ⭐️
  • 구체적 정보(연관값)를 변수에 바인딩(let/var가능)하는 패턴
  • [열거형 case 패턴]
    • case Enum.case(let 변수이름):
    • case let Enum.case(변수이름):
  • [스위치문 뿐만 아니라, 조건문/반복문에서도 활용가능]
    • switch문 (대부분)
    • if / guard / for-in / while (필요한 경우)
enum Computer {                         // 3가지로 정의
    case cpu(core: Int, ghz: Double)
    case ram(Int, String)
    case hardDisk(gb: Int)
}

var chip = Computer.cpu(core: 8, ghz: 3.1)

switch chip {                          // 수십가지로도 처리 가능 (필요한 처리 확장 가능)
case .cpu(core: 8, ghz: 3.1): // Computer.chip과 동일
    print("CPU 8코어 3.1GHz입니다.")
case .cpu(core: 8, ghz: 2.6):
    print("CPU 8코어 2.6GHz입니다.")
case .cpu(core: 4, ghz: let ghz):       // let ghz = 연관값  (cpu가 4코어인 경우, ghz에 저장된 연관값을 꺼내서 바인딩)
    print("CPU 4코어 \(ghz)HGz입니다.")
case .cpu(core: _, ghz: _):
    print("CPU 칩 입니다.")
case .ram(32, _):
    print("32기가램 입니다.")
case .ram(_, _):
    print("램 입니다.")
case let .hardDisk(gb: gB):            // let gB = 연관값
    print("\(gB)기가 바이트 하드디스크 입니다.")
default:                               // 대부분 default문이 필요하기도 함
    print("그 외에 나머지는 관심이 없습니다. 그렇지만 칩이긴 합니다.")
}
// 연관값을 가지고 있는 열거형의 경우, 원하는 로직으로 잘 정의해서 처리해야하는 것에 주의
  1. 연관값(Associated Values)이 있는 경우, if / guard / for-in / while 문 (열거형 case 패턴)
    • 값 하나를 사용하기 위해서, 스위치문을 전체를 다 써야하는 불편하이 존재
    • switch chip { case Computer.hardDisk(gb: let gB): print("\(gB)기가 바이트 하드디스크임") default: break }
    • 특정 케이스만 다루기 위해서 if문이나 반복문(for문) 사용 가능
    • chip = Computer.hardDisk(gb: 128) if case Computer.hardDisk(gb: let gB) = chip { print("Check!") } // 반복문 사용 for case let .cpu(core: c, ghz: h) in chiplists { // 배열중에서 특정 케이스만 뽑아서 활용 가능 ⭐️ print("CPU칩: \(c)코어, \(h)헤르츠") } // 일반 for문과 비교 =====> 모든 경우 for chip in chiplists { print("\(chip)") }
    • 열거형 케이스 패턴(for문) - 옵셔널타입 요소 패턴
    • // 옵셔널 타입을 포함하는 배열에서 반복문을 사용하는 경우 let arrays: [Int?] = [nil, 2, 3, nil, 5] for case .some(let number) in arrays { // let .some(number) print("Found a \(number)") }
    •  
     


반응형