티스토리 뷰

Swift

Swift ) Nested Types

Zedd0202 2017. 12. 6. 17:41
반응형

안녕하세요:) Zedd입니다.

오랜만에 번역 ~.~

Nested Types은 뭔가 잘쓰면 코드가 정말 많이 줄어들 수 있는 방법 중 하나라고 생각해요 :)



 Nested Types


열거형(Enum)은 종종 특정 클래스나 구조체 기능을 지원하기 위해 만들어집니다.

마찬가지로, 보다 복잡한 타입의 컨텍스트내에서 사용하기 위해, 유틸리티 클래스 및 구조체를 정의하는 것이 편리할 수 있습니다.

이를 달성하기 위해 Swift는 중첩된 타입(Nested Types)을 정의할 수 있습니다. 즉, 지원하는 타입의 정의내에서 클래스 및 구조체, 열거형을 중첩할 수 있습니다.

타입을 다르 타입에 중첩시킬려면 지원하는 타입의 외부 중괄호 안에 해당 정의를 작성하세요. 타입은 필요한 수준만큼 중첩될 수 있습니다. 


Nested Types in Action

아래 예제는 BlackjackCard(블랙잭카드)라는 구조체를 정의합니다. BlackjackCard는 Blackjack게임에서 사용되는 카드를 모델링합니다.
BlackjackCard 구조체에는 Suit및 Rank라는 두개의 중첩된 열거타입이 있습니다. 
Blackjack에서는 Ace카드의 값이 1 또는 11입니다.
이 기능은 Values라는 구조체로 표현되며, Rank열거형 내에 중첩되어 있습니다. 

  1. struct BlackjackCard {

        

        // nested Suit enumeration

        enum Suit: Character {

            case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"

        }

        

        // nested Rank enumeration

        enum Rank: Int {

            case two = 2, three, four, five, six, seven, eight, nine, ten

            case jack, queen, king, ace

            struct Values {

                let first: Int, second: Int?

            }

            var values: Values {

                switch self {

                case .ace:

                    return Values(first: 1, second: 11)

                case .jack, .queen, .king:

                    return Values(first: 10, second: nil)

                default:

                    return Values(first: self.rawValue, second: nil)

                }

            }

        }

        

        // BlackjackCard properties and methods

        let rank: Rank, suit: Suit

        var description: String {

            var output = "suit is \(suit.rawValue),"

            output += " value is \(rank.values.first)"

            if let second = rank.values.second {

                output += " or \(second)"

            }

            return output

        }

    }


자..코드가 길어서 놀라셨을 텐데요.

하나씩 봅시다. 

  1. struct BlackjackCard {


        // nested Suit enumeration

        enum Suit: Character {

            case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"

        }


BlackjackCard라는 구조체를 하나 만들어줬네요!! 그리고 그 안에!!!!!! enum!!

구조체 안에 Suit라는 열거형을 하나 만들어줬습니다. case들의 raw value가 Character이므로, 반드시 raw type을 지정해줘야해요.


  1. enum Rank: Int {

            case two = 2, three, four, five, six, seven, eight, nine, ten

            case jack, queen, king, ace

            struct Values {

                let first: Int, second: Int?

            }

            var values: Values {

                switch self {

                case .ace:

                    return Values(first: 1, second: 11)

                case .jack, .queen, .king:

                    return Values(first: 10, second: nil)

                default:

                    return Values(first: self.rawValue, second: nil)

                }

            }

        }

자..그리고 그 밑에 Rank라는 enum을 또!!! 정의해줬네요.

위 코드에는 안썼지만, BlackjackCard 구조체 안이라는 것을 기억해주세요.

case들의 raw value가 역시 Int기 때문에 raw type을 Int로 주었습니다.


그리고!!! 이 Rank라는 enum안에!!! 또 구조체를 정의했네요. 지금까지 구조로 보면 struct ⊂ enum,enum ⊂ struct 이런 구조로 되어있네요. 

Values라는 구조체안에, first와 second를 선언해줬죠? 

first는 Int로. second는 Int?로 선언해준 것!!! 보이시나요?

제가 BlackjackCard를 잘 모르지만...일단 ace는 값을 2개를 가지고 있나봐요.  

이 이외의 카드들은 한가지 값만 가지구요.

그러니까 모든 카드들은 first value는 있지만,  ace만이 값을 하나 더 가지고 있으므로 second를 Int?로 선언해준것이죠. 평범한 카드였다면 두번째 값은 아예 없어야 하니까요.


그리고 values라는 방금 만든 Values라는 구조체 타입의 프로퍼티를 정의해줬습니다. 딱 보니까 연산프로퍼티 같죠? 

enum에서는 저장프로퍼티는 만들 수 없지만, 연산프로퍼티는 만들 수 있었죠?

연산프로퍼티를 만들어 준거에요!!

여기서 self는 Rank입니다. 그리고 각 케이스마다 Values타입의 인스턴스를 리턴하죠?  ace일때는 first와 second 둘다에 값을 준채로. 나머지 때는 second를  nil로 줬네요. second가 Int?타입이라 가능한 일입니다.


마지막의 default 일때는 self.rawValue를 first값으로 주는데요, Rank의 첫번째 case. two부터 2라는 값을 줬죠? 그럼 나머지 케이스에도 값이 메겨지게 되는데, 1씩 증가한 값이 넣어지게 됩니다. 그러니까 three의 rawValue는 3인거죠. 

이렇게 리턴하는 Values의 인스턴스의가 values에 할당되지 않는다는 것!!! 기억나시죠?

연산프로퍼티에 대해 잘 모르신다면, <연산프로퍼티> 참고XD



  1. let rank: Rank, suit: Suit

        var description: String {

            var output = "suit is \(suit.rawValue),"

            output += " value is \(rank.values.first)"

            if let second = rank.values.second {

                output += " or \(second)"

            }

            return output

        }


자 아직 안끝났어요!!!! 지금 우리는 아직 BlackjackCard라는 구조체 안에 있답니다. 

그럼 위 코드 제일 첫번째 줄에서 선언한 rank와 suit는 BlackjackCard struct의 저장프로퍼티네요!(짱중요)

그것도 아까 선언한 Rank, Suit 열거형 타입으로요.

역시나 description도 BlackjackCard 구조체의 연산프로퍼티네요. 연산프로퍼티 안에서 또, output이라는 것을 선언했네요. 이 output은 이 description안에서만 쓸 수 있는 지역 변수입니다.

description은 String을 리턴하는데요. suit의 rawValue는 

  1.     enum Suit: Character {


            case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"

        }


저렇게 하트인지 클로버 인지~~~ 나타내주는 Character였어요. 

그럼 지금까지 output이라는 String은 "suit is (위 4가지 그림 중 하나)" 가 되겠네요.

그리고 output에 


  1.    output += " value is \(rank.values.first)"


rank의 values의 first값을 넣어주네요. String이니까 +는 concatenate겠죠?

자..rank는 Rank타입이었죠?

그 안에 Values라는 구조체가 있었고, 이 Values라는 구조체 타입의 연산프로퍼티가 values였어요. 이 구조체와 연산프로퍼티는 Rank라는 열거형안에서 정의된것이므로, rank라는 Rank타입 인스턴스에서 당연히 접근이 가능합니다. 그 values는 Values타입이었고, 그 Values는 first와 second를 가지고 있었죠? 

그중에서 first만 output에 추가해줬네요. 

잘 이해가 안가시면, 위에 Rank 소스코드를 계속 보시면서 설명을 읽어보세요!!!


자,..그럼 지금까지의 output은 "suit is (네가지 그림 중 하나), value is (해당 카드의 값)"

이 되겠네요. 그리고 아직 끝난게 아니죠? 


  1.    if let second = rank.values.second {

                output += " or \(second)"

            }

            return output


만약 해당 카드가  ace였다면? 그러니까 if let(옵셔널 바인딩) 구문으로 이 해당 values에 second가 있는지 검사해줬죠? 

만약 있다면!! 즉 ace카드였다면, output에 second값을 넣어주는 것을 볼 수 있습니다 .

그럼 지금까지의 output값은 이 구문까지 실행했다는 것은 해당 카드가 ace라는 소리니까 ace임을 가정하고 concatenate해볼게요.

"suit is (네가지 그림 중 하나) , value is 1 or 11"겠네요. 

ace는 1또는 11의 값을 가지니까요!


그럼 이제야 비로소 BlackjackCard라는 구조체를 마치게 됩니다. 그럼 이제 진짜 BlackjackCard 구조체의 인스턴스를 생성해볼까요? BlackjackCard는 구조체니까 암시적 이니셜라이저가 존재합니다. 


  1. let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)


    print("theAceOfSpades: \(theAceOfSpades.description)")

자... 한번 봅시다. theAceOfSpades라는 BlackjackCard타입의 인스턴스를 만들었습니다. rank와 suit는 BlackjackCard의 저장프로퍼티였죠? 그러니까 자동으로 이니셜라이져로 생성되게 됩니다. 우리는 rank를 ace를 주고, suit(그 카드 모양)을 스페이드로 줬네요!!!

description은 BlackjackCard에 연산프로퍼티가 하나 있었죠? 바로 위에서 output을 리턴해주는 연산프로퍼티였죠.

그럼 어떤게 프린트 될까요? 

theAceOfSpades: suit is , value is 1 or 11


네!! 이게 프린트 되게 되죠.
Rank와 Suit가 BlackjackCard에 중첩되어있지만, 타입을 컨텍스트에서 유추할 수 있으므로 인스턴스를 초기화하면, case 이름만으로 열거 case를 참조할 수 있게 됩니다. 



Referring to Nested Types


정의 컨텍스트 외부에서 중첩된 타입을 사용하려면, 해당 이름앞에 nested type의 이름을 붙이세요.


  1. let heartsSymbol = BlackjackCard.Suit.hearts.rawValue

    // heartsSymbol is "♡"


그러니까!! BlackjackCard라는 인스턴스를 안만들고 해당 값에 접근하고 싶다면,

위 코드처럼 nested type의 이름을 붙이면 됩니다. 


오랜만에 Swift번역ㅎㅎㅎ재밌네요.

Nested Types을 딱 한번..써봤는데 정말 코드가 많이 줄어들고 정말 직관적이게 되더라구요 :) 많이들 쓰셨으면 좋겠습니다.

도움이 되었길 바라며 ~_~


반응형

'Swift' 카테고리의 다른 글

Swift ) Protocols (3)  (1) 2017.12.15
Swift ) Unicode to Int/Int to Unicode  (2) 2017.12.14
Swift) dynamic이란? / Realm의 dynamic var는?  (8) 2017.11.17
Swift ) Patterns  (0) 2017.10.31
Swift ) Type Casting  (2) 2017.10.23