🔥 연관값

944자
11분

이전 섹션의 예제들은 열거형의 케이스가 어떻게 그 자체로 정의된(그리고 타입이 지정된) 값인지 보여주었습니다. 상수나 변수를 Planet.earth로 설정하고 나중에 이 값을 확인할 수 있죠. 하지만 때로는 이러한 케이스 값과 함께 다른 타입의 값을 저장하는 것이 유용할 때가 있습니다. 이러한 추가 정보를 연관 값(associated value)이라고 하며, 코드에서 해당 케이스를 값으로 사용할 때마다 달라집니다.

Swift 열거형은 주어진 모든 타입의 연관 값을 저장하도록 정의할 수 있으며, 필요한 경우 열거형의 각 케이스에 대해 값 타입이 다를 수 있습니다. 이와 유사한 열거형을 다른 프로그래밍 언어에서는 discriminated unions, tagged unions 또는 variants라고 합니다.

예를 들어, 재고 추적 시스템이 두 가지 다른 타입의 바코드로 제품을 추적해야 한다고 가정해 봅시다. 일부 제품에는 숫자 0부터 9까지 사용하는 UPC 형식의 1D 바코드 레이블이 붙어 있습니다. 각 바코드에는 숫자 시스템 자릿수, 5자리 제조업체 코드 자릿수, 5자리 제품 코드 자릿수가 있습니다. 그 뒤에는 코드가 올바르게 스캔되었는지 확인하기 위한 검사 자릿수가 옵니다:

lecture image

다른 제품에는 ISO 8859-1 문자를 사용할 수 있고 최대 2,953자까지 문자열을 인코딩할 수 있는 QR 코드 형식의 2D 바코드 레이블이 붙어 있습니다:

lecture image

재고 추적 시스템에서 UPC 바코드는 4개의 정수 튜플로, QR 코드 바코드는 임의 길이의 문자열로 저장하는 것이 편리합니다.

Swift에서는 두 가지 타입의 제품 바코드를 정의하는 열거형이 다음과 같을 수 있습니다:

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
swift

이는 다음과 같이 읽을 수 있습니다:

"Barcode라는 열거형 타입을 정의하는데, 연관 값 타입이 (Int, Int, Int, Int)인 upc 값이나 연관 값 타입이 StringqrCode 값 중 하나를 가질 수 있다."

이 정의는 실제 IntString 값을 제공하지 않습니다. 단지 Barcode 상수와 변수가 Barcode.upc 또는 Barcode.qrCode와 같을 때 저장할 수 있는 연관 값의 타입만 정의합니다.

그런 다음 두 가지 타입 중 하나를 사용하여 새 바코드를 만들 수 있습니다:

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
// productBarcode는 연관 튜플 값 (8, 85909, 51226, 3)을 가진 Barcode.upc 값으로 초기화됩니다.
swift

이 예제는 productBarcode라는 새로운 변수를 생성하고 (8, 85909, 51226, 3)이라는 연관 튜플 값을 가진 Barcode.upc 값을 할당합니다.

동일한 제품에 다른 타입의 바코드를 할당할 수 있습니다:

productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
// 이제 productBarcode는 "ABCDEFGHIJKLMNOP"라는 문자열 값을 가진 Barcode.qrCode 값입니다.
swift

이 시점에서 원래의 Barcode.upc와 그 정수 값들은 새로운 Barcode.qrCode와 그 문자열 값으로 대체됩니다. Barcode 타입의 상수와 변수는 .upc 또는 .qrCode 중 하나를 저장할 수 있지만(연관 값과 함께), 특정 시점에는 그 중 하나만 저장할 수 있습니다.

Matching Enumeration Values with a Switch Statement 예제와 유사하게 switch 문을 사용하여 다른 바코드 타입을 확인할 수 있습니다. 하지만 이번에는 연관 값이 switch 문의 일부로 추출됩니다. 각 연관 값을 switch case 본문 내에서 사용하기 위해 상수(let 접두사 사용) 또는 변수(var 접두사 사용)로 추출합니다:

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
    // 연관 값을 상수로 추출하여 출력합니다.
case .qrCode(let productCode):
    print("QR code: \(productCode).")
    // 연관 값을 상수로 추출하여 출력합니다.
}
// "QR code: ABCDEFGHIJKLMNOP."가 출력됩니다.
swift

열거형 케이스의 모든 연관 값이 상수로 추출되거나 변수로 추출되는 경우, 간결함을 위해 let이나 var 주석을 case 이름 앞에 한 번만 배치할 수 있습니다:

switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
    // 모든 연관 값을 한 번에 상수로 추출하여 출력합니다.
case let .qrCode(productCode):
    print("QR code: \(productCode).")
    // 연관 값을 상수로 추출하여 출력합니다.
}
// "QR code: ABCDEFGHIJKLMNOP."가 출력됩니다.
swift

위의 코드를 좀 더 자세히 살펴보면:

case let .upc(numberSystem, manufacturer, product, check):
swift
  • 이 라인은 productBarcode.upc 케이스에 해당하는 경우를 처리합니다.
  • let 키워드를 사용하여 .upc의 모든 연관 값을 상수로 한 번에 추출하고 있어요.
  • numberSystem, manufacturer, product, check라는 상수 이름으로 연관 값에 접근할 수 있게 됩니다.
case let .qrCode(productCode):
swift
  • 이 라인은 productBarcode.qrCode 케이스에 해당하는 경우를 처리하고 있습니다.
  • 마찬가지로 let 키워드로 .qrCode의 연관 값을 productCode라는 상수로 추출하고 있죠.

이렇게 추출된 연관 값들은 print 문에서 사용되어 바코드의 세부 정보를 출력하는 데 활용되고 있습니다.

열거형의 연관 값 기능을 사용하면 열거형의 케이스에 추가 정보를 유연하게 저장할 수 있어 코드의 표현력을 높일 수 있답니다. 바코드 예제처럼 서로 다른 타입의 데이터를 열거형 케이스와 연관지어 다룰 수 있어 매우 유용하죠!

열거형과 연관 값에 대해 좀 더 알아보려면 다음 표를 참고해 보세요:

개념설명
열거형 (Enumeration)관련된 값들의 그룹을 정의하는 타입. 케이스들로 구성됨.
열거형 케이스 (Enumeration Case)열거형을 구성하는 개별 값들. 연관 값을 가질 수 있음.
연관 값 (Associated Value)열거형 케이스와 함께 저장되는 추가적인 정보. 다양한 타입의 값이 될 수 있음.
switch 문 (Switch Statement)열거형 값을 매칭하고 연관 값을 추출하는 데 사용됨.

연관 값을 사용하면 열거형을 더욱 풍부하고 유연하게 활용할 수 있습니다. 예를 들어 다음과 같이 다양한 모양을 표현하는 열거형을 만들 수 있겠죠:

enum Shape {
    case rectangle(width: Double, height: Double)
    case circle(radius: Double)
    case point
}
 
let rect = Shape.rectangle(width: 10, height: 20)
let circ = Shape.circle(radius: 5)
let point = Shape.point
swift

위 코드에서 Shape 열거형은 세 가지 케이스를 가지고 있습니다:

  • rectangle 케이스는 widthheight라는 두 개의 Double 연관 값을 가집니다.
  • circle 케이스는 radius라는 하나의 Double 연관 값을 가지죠.
  • point 케이스는 연관 값이 없는 경우입니다.

이렇게 연관 값을 사용하면 각 케이스에 맞는 추가 정보를 저장할 수 있어 열거형의 활용도가 더욱 높아집니다.

연관 값의 또 다른 유용한 사용 예로 네트워크 요청의 결과를 표현하는 것이 있습니다:

enum NetworkResult {
    case success(Data)
    case failure(Error)
}
 
func handleResult(result: NetworkResult) {
    switch result {
    case .success(let data):
        print("Received data: \(data)")
    case .failure(let error):
        print("Error occurred: \(error)")
    }
}
swift

여기서 NetworkResult 열거형은 네트워크 요청의 가능한 결과를 나타냅니다.

  • 요청이 성공하면 받은 데이터를 success 케이스의 연관 값으로 저장하고,
  • 요청이 실패하면 발생한 오류를 failure 케이스의 연관 값으로 저장하죠.

그리고 handleResult 함수에서는 switch문을 통해 NetworkResult를 처리하면서 연관 값을 추출하여 사용하고 있습니다.

이렇게 열거형의 연관 값 기능은 다양한 상황에서 코드를 더욱 명확하고 표현력 있게 작성하는 데 활용될 수 있답니다. 열거형과 연관 값을 적재적소에 사용하면 Swift 코드의 가독성과 유지보수성을 높일 수 있겠죠?