# 중첩타입 개념

타입 내부에 타입을 선언하는 것을 중첩타입이라고 한다. 중첩 타입의 선언은 언제나 가능하다.

class Aclass {
    struct Bstruct {
        var name: Cenum
    }
}


// 타입 선언과 인스턴스의 생성

let aClass: Aclass = Aclass()

// 멤버와이즈 이니셜라이저 호출
let bStruct: Aclass.Bstruct = Aclass.Bstruct(name: .bCase)

중첩타입을 사용하는 이유는 아래와 같다.

  1. 타입간의 연관성에 따라 특정 타입 내에서만 사용하기 위해서
  2. 타입간의 연관성에 따라 내부 구조를 디테일하게 설계
struct BlackjackCard {

    // Suit(세트) 열거형 - 중첩
    enum Suit: Character {     // 원시값(rawValue)사용
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }

    // 순서(숫자) 열거형 - 중첩
    enum Rank: Int {     // 원시값(rawValue)사용
        case two = 2, three, four, five, six, seven, eight, nine, ten
        case jack, queen, king, ace   // (원시값 존재하지만 사용하지 않고자 함 ===> values)

        // Values 타입정의 (두개의 값을 사용) //===> 열거형 값(순서)을 이용 새로운 타입을 반환하기 위함

        // 기본 지정 생성자 제공
        struct Values {
            // 에이스 카드의 두 가지 역할로 인해 second라는 속성이 생긴것
            let first: Int, second: Int?
        }

        // (읽기) 계산 속성 (열거형 내부에 저장 속성은 선언 불가)
        var values: Values {
            switch self {
            case Rank.ace:
                return Values(first: 1, second: 11)    // 에이스 카드는 1 또는 11 로 쓰임
            case .jack, .queen, .king:
                return Values(first: 10, second: nil)  // 10으로 쓰임
            default:
                return Values(first: self.rawValue, second: nil)    // 2 ~ 10까지의 카드는 원시값으로 쓰임
            }
        }
    }

    // 블랙잭 카드 속성 / 메서드  =======================================
    // 어떤 카드도, 순서(숫자)와 세트(Suit)를 가짐
    let rank: Rank, suit: Suit

    // (읽기) 계산속성
    var description: String {
        get {
            var output = "\(suit.rawValue) 세트,"
            output += " 숫자 \(rank.values.first)"

            if let second = rank.values.second {   // 두번째 값이 있다면 (ace만 있음)
                output += " 또는 \(second)"
            }

            return output
        }
    }
}

개발 단계에서 좀 더 시맨틱한 개발을 위해 사용한다고 보면 된다. 아래 두 코드를 비교해보자

enum Style {
    case full
    case long
    case medium
    case none
}

struct DateFormatters {
    var style: Style
}

var dateStyle1 = DateFormatters(style: .full)
dateStyle1 = DateFormatters(style: Style.full)
dateStyle1.style = Style.full
dateStyle1.style = .full

위 코드는 Style 열거형을 DateFormatters 구조체로부터 분리하여 코드를 작성하였다. 이렇게 되면 변수 선언과 타입 작성에는 더 편하지만 dateStyle1.style = Style.full의 경우 어떤 스타일에 대한 이야기인지 명확하게 파악하기가 어렵다.

아래 코드를 다시 봐보자.

struct DateFormatters {
    var style: DateFormatters.Style

    // 중첩타입으로 선언
    enum Style {
        case full
        case long
        case medium
        case none
    }
}

var dateStyle2 = DateFormatters(style: .full)
dateStyle2 = DateFormatters(style: DateFormatters.Style.full)
dateStyle2.style = DateFormatters.Style.full
dateStyle2.style = .long

세 번째 코드의 경우 dateStyle2.style = DateFormatters.Style.full를 보면 타입에 대해 명확히 지정해줌으로써 dateStyle2라는 객체 내의 style 속성이 어떤 타입과 연관되어 있는 것인지 명확하게 파악이 가능하다.

중첩타입 파악하기

  1. DateFormatter.Style.full과 같이 중간에 대문자로 시작하는 접근 연산이 나타나면 중첩타입임을 알아채자
  2. 실제 앱을 제작할때 중첩 타입을 활용하여 타입 간의 연관성을 명확하게 하자.

# 예시

struct K {
    static let appName = "MySuperApp"
    static let cellIdentifier = "ReusableCell"
    static let cellNibName = "MessageCell"
    static let registerSegue = "RegisterToChat"
    static let loginSegue = "LoginToChat"

    struct BrandColors {
        static let purple = "BrandPurple"
        static let lightPurple = "BrandLightPurple"
        static let blue = "BrandBlue"
        static let lighBlue = "BrandLightBlue"
    }

    struct FStore {
        static let collectionName = "messages"
        static let senderField = "sender"
        static let bodyField = "body"
        static let dateField = "date"
    }
}



// 문자열 대신에 아래처럼 사용할 수 있음 ⭐️ (문자열 실수는 치명적인 에러를 발생시킴)

let app = K.appName      // "MySuperApp"
let color = K.BrandColors.blue    // "BrandLightBlue"

각종 데이터 상수화를 위해 중첩 타입을 사용한다.

아래 코드는 메세지 앱 제작 모델에 관련된 예시 코드이다. 지나치지 말고 복습할때 다시 읽어보기

class Message {
    // 상태를 중첩타입으로 (외부에서 접근못하게 하려면, private으로 선언가능)
    private enum Status {
        case sent
        case received
        case read
    }

    // 보낸 사람, 받는 사람
    let sender: String, recipient: String, content: String

    // 보낸 시간
    let timeStamp: Date

    // 메세지 상태 정보 (보냄/받음/읽음)
    private var status: Message.Status = Message.Status.sent

    init(sender: String, recipient: String, content: String) {
        self.sender = sender
        self.recipient = recipient
        self.content = content

        self.timeStamp = Date()   // 현재시간 생성 ===> 시간은 유저가 주는 정보 아님
    }

    func getBasicInfo() -> String {
        return "보낸사람: \(sender), 받는사람: \(recipient), 메세지 내용: \(content), 보낸 시간: \(timeStamp.description), "
    }

    // 메세지 상태에 따라서, 색깔 바뀜
    func statusColor() -> UIColor {
        switch status {
        case .sent:
            return UIColor(red: 1, green: 0, blue: 0, alpha: 1)
        case .received:
            return UIColor(red: 0, green: 0, blue: 1, alpha: 1)
        case .read:
            return UIColor(red: 0, green: 1, blue: 1, alpha: 1)
        }
    }
}

let message1 = Message(sender: "홍길동", recipient: "임꺽정", content: "뭐해?")
print(message1.getBasicInfo())

sleep(1)

let message2 = Message(sender: "임꺽정", recipient: "홍길동", content: "그냥있어")
print(message2.getBasicInfo())

# Reference

  1. 인프런 - 앨런 swift 문법 마스터 스쿨 (opens new window)