ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift - Initializer - Inheritance, Overriding
    Programming/Swift 2021. 12. 11. 20:19

    안녕하세요 BeePeach입니다 :)

     

    오늘 공부해볼 주제는 Init의 상속(Ingeritance)과 재정의(Override)입니다.

    Class는 상속과 재정의를 제공하죠?? 그럼 init은 상속이 가능한지? 가능하다면 언제 가능한지?, init도 override가 가능한지에 대해서 공부해보도록 하겠습니다.

     

     

    Swift에서는 기본적으로 init은 상속이 불가능합니다.

    왜 그런지는 잠깐 생각해보면 알 수 있습니다.

    Superclass의 init이 그대로 상속이 된다면 만약 기본값이 없는 subclass에 정의된 프로퍼티는 초기화가 완벽하게 이루어지지 않은 채로 인스턴스가 생성되게 되겠죠???

    이러한 이유로 기본적으로는 init의 상속은 제한되어 있습니다. 그리고 이 말을 다시 뒤집어 생각하면 위 상황만 없다면 상속이 가능하다는 말이 됩니다. 이러한 경우를 Automatic initializer Inheritance(생성자 자동 상속)이라고 합니다.

     

    이렇게 기본적으로 상속을 제공하지 않으므로 superclass의 init을 구현하기 위해서는 override(재정의)를 이용해야 합니다.

    그럼 상속에 대해서 공부하기 전에 재정의에 대해서 먼저 자세히 공부해보도록 하겠습니다!

     

     


    Initializer Overriding

     

    Superclass의 designated init을 subclass에서 구현하려면 반드시 override 키워드를 추가시켜 재정의 해야 합니다.

    (super.init으로 호출하는 것과 재정의하여 custom 하는 것을 헷갈리면 안 됩니다!)

     

    여기서 눈여겨볼 단어는 superclass의 designated init입니다.

    Superclass의 어떠한 designated init이라도 반드시 subclass에서 구현하기 위해서는 override 해야 합니다.

    하지만 superclass의 convenience init을 subclass에서 구현할 때 override를 사용하지 않아도 됩니다.

    그 이유는 subclass에서는 superclass의 convenience init에 접근할 수 없기 때문입니다. 그래서 재정의가 아닙니다.

    새로 구현하기 때문에 override에 필요 없습니다.

    그럼 한번 예시를 볼까요??

     

     

    대충 보면 문제가 없는 거 같습니다.

    하지만 이 코드의 문제는 superclass에서 init(name:) 이라는 생성자가 존재하는데 subclass에서 init(name:)을 또 구현한다는 점입니다.

     

     

    이렇게 init 앞에 override를 적어주면 Swift가 superclass에 재정의할 init이 일치하는지 확인하고 재정의 생성자의 파라미터가 의도대로 작성되었는지 확인하게 됩니다.

     

     

    이번에는 superclass에 convenience init을 정의한 후에 subclass에서 똑같은 이름으로 구현을 했습니다.

    하지만 designated init이 아니기 때문에 override 키워드를 붙이지 않은 것을 확인할 수 있습니다.

     

    마지막으로 조금 헷갈릴만한 init을 생성해보도록 하겠습니다.

    Designated init과 똑같은 이름의 convenience init을 subclass에서 구현한다면 어떻게 될까요???

    코드를 만들기 전에 생각을 해보면 convenience이기 때문에 override를 적을 필요가 없을까요??

    아니면 superclass의 designated init을 구현하는 것이기 때문에 override를 적어야 하는 것일까요???

     

     

    정답은 바로 override를 붙여야 한다입니다.

     

    위 코드를 보면 일단 init이 겹치는 것을 방지하기 위해서 Rectangle class의 init을 변경해줬습니다.

    잘 생각해보면 subclass에서 convenience init으로 구현하지만 결국 superclass의 designated init을 구현해야 하는 것이기 때문에 override를 붙여야 하는 것입니다.

    위에서 말씀드렸던 Superclass의 어떠한 designated init이라도 반드시 subclas에서 구현하기 위해서는 override 해야 합니다.

    를 다시 생각해보시면 됩니다.

     

    만약 override를 적지 않는다면 아래와 같은 에러가 발생합니다.

    만약 override를 적지 않는다면 이러한 에러가 나옵니다.

     

     


     

    Initializer Inheritance

     

    Override에 대해서 알아보았으니 이제 상속에 대해서 공부를 해보겠습니다.

    처음에 init은 기본적으로 상속되지 않는다고 했습니다. 하지만 특정 조건을 만족한다면 상속이 된다고 했습니다.

    그럼 이 특정 조건이 무언인지 볼까요??

     

    우선 기본적으로 subclass에서 새롭게 추가된 stored property가 초기화되어있어야 합니다.

    (이는 default value로 초기화되거나 새로운 init을 구현하여 초기화되거나 둘 중 하나의 방법으로 초기화가 되어있어야 한다는 의미입니다.)

    새롭게 추가된 property가 초기화되지 않았다면 superclass의 init에서는 이 property에 대해서는 초기화를 하지 않고 있기 때문에 반드시 초기화가 이루어져있어야 한다는데 바탕으로 들어가야 합니다.

    그리고 이 조건이 만족된 상태에서 2가지의 각 조건에 따라 상속되는 init이 달라지게 됩니다.

     

    1. Subclass에서 designated init을 선언하지 않았다면 superclass의 모든 designated init이 자동으로 상속됩니다.

     

    2. 1번 상황 또는 overriding 하여 superclass의 모든 designated init을 구현했다면 superclass의 모든 convenience init이 자동적으로 상속됩니다.

    (이 조건은 subclass에서 convenience init으로 override해도 만족합니다. override를 꼭 superclass의 designated init -> subclass designated init으로 할 필요는 없습니다.)

     

    다시 풀어서 설명하면 subclass에서 새로운 stored property의 기본값을 모두 정의하고 designated init을 구현하지 않았다면 superclass의 모든 designated init이 상속됩니다.

    그리고 1번 상황으로 모든 designated init을 상속받는 경우이거나 상속받지 못해도 직접 override를 통해서 superclass의 designated init을 모두 재정의한다면 superclass의 모든 covenience init이 상속됩니다.

    즉 만약 subclass를 생성하고 새로운 stored property가 기본값을 가지고 init을 하나도 정의하지 않는다면 1번 상황을 만족하고 그렇게 되면 2번 상황도 만족하게 되므로 superclass의 모든 init을 상속받게 됩니다.

     

    2번 조건에서 convenience init으로 override 한다는 의미가 혹시 이해가 되지 않는다면 아래 예제에서 자세히 설명하도록 하겠습니다.

     

     


     

    예제

     

    이해를 쉽게 하기 위해서 예제를 살펴보겠습니다.

    AppleDeveloper문서에서 상속 체인을 보기 쉽게 그림으로 알려주고 있기 때문에 예시 코드를 그대로 사용하겠습니다.

     

     

    먼저 base class인 Food를 정의했습니다.

    간단하게 name 프로퍼티만 가지고 있습니다. 그리고 init은 designated init과 convenience init을 각각 하나씩 가지고 있습니다.

    convenience init은 반드시 같은 class의 init을 호출해야 하고 궁극적으로 designated init을 호출해야 한다는 delegate across 규칙도 지키고 있는 것을 확인할 수 있습니다.

     

     

    자 그럼 이제 Food를 상속하는 subclass를 선언해 보겠습니다.

     

     

    RecipeIngredient class는 Food를 상속하고 있습니다.

    그럼 상속받은 name 프로퍼티를 가지게 됩니다. 그리고 새로운 quantity 프로퍼티를 가지고 있습니다. quantity가 기본값을 가지고 있지 않기 때문에 초기값을 설정해주기 위해서 init을 새로 생성해야 합니다.

     

    먼저 designated init(name:quantity:)을 구현했습니다.

    Subclass의 designated init은 반드시 superclass의 designated init을 호출해야 한다는 delegate up 규칙도 지키고 있으며 super.init을 호출하기 전에 상속받지 않은 프로퍼티가 초기화되어 있어야 한다는 Safety check1도 만족하고 있는 것을 확인할 수 있습니다.

     

    그럼 여기까지만 구현한다면 Food의 init을 상속받을 수 있을까요??

    다시 위로 올라가서 상속받기 위한 조건을 생각해보면 기본 베이스인 새로 추가된 stored property가 초기화되어있어야 한다는 만족합니다.

    quantity 프로퍼티를 초기화 하기 위해서 init(name:quantity:) 생성자를 구현했기 때문입니다.

     

    하지만 초기화하기 위해 designated init(name:quantity:)을 선언했기 때문에 1번 조건은 만족할 수 없습니다.

    그럼 2번 조건은 만족할 수 있을까요??

    아쉽게도 Food에서 선언되어있는 designated init(name:)을 상속받지도 못했고 override도 안 했기 때문에 convenience init도 상속받지 못했습니다.

     

     

    확인해보면 init()은 상속받지 못했기 때문에 사용하려고 하면 에러가 발생하는 것을 확인할 수 있습니다.

    그럼 이제 2번 조건을 만족하도록 해보겠습니다.

     

     

    코드를 보면 convenience init을 하나 추가시켰습니다. 그런데 그 init은 init(name:)으로 superclass에서 이미 존재하는 init(name:)입니다.

    Subclass에서는 convenience init이지만 이 init(name:)은 superclass의 designated init입니다.

    맨 위에 init override에서 superclass의 designated init은 어떠한 경우에도 subclass에서 구현하기 위해서는 override 키워드를 추가시켜야 한다고 했습니다. 그래서 override 키워드가 추가된 것입니다.

    이렇게 결국에는 Food에 있는 모든 designated init을 구현했습니다. 그럼 조건 2를 만족하게 돼서 Food의 convenience init이 상속됩니다.

     

    이전에 사용하지 못하던 init()을 사용해도 에러가 발생하지 않고 초기화가 잘 이루어지는 것을 확인할 수 있습니다. 

    상속 체인을 나타내면 아래 그림과 같습니다.

     

     

    이제 마지막으로 하나 더 class를 추가시켜보도록 하겠습니다.

     

     

    ShoppingListItem class는 RecipeIngredient class를 상속받고 있습니다.

    두 가지 프로퍼티를 가지고 있는데 purchased 프로퍼티에 기본값을 할당했습니다.

    그리고 description이라는 computed property를 선언하고 있습니다.

     

    ShoppingListItem은 기본적으로 새로운 stored property가 기본값을 가지고 있으며 designated init을 선언하지 않았습니다.

    그럼 조건 1을 만족하여 Food의 모든 designated init을 상속받습니다. 그리고 이렇게 되면 자동적으로 조건 2를 만족하면서 Food의 모든 convenience init도 상속받게 됩니다.

     

    그래서 우리는 어떠한 init도 구현하지 않았지만 init(), init(name:), init(name:quantity:) 세 가지 생성자를 모두 사용할 수 있습니다.

     

     

    위 예제에서 모든 생성자를 사용할 수 있는 것을 확인할 수 있습니다.

     

     

     

     


     

    그림 출처 :  https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

     

    Initialization — The Swift Programming Language (Swift 5.5)

    Initialization Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization t

    docs.swift.org

     

    728x90

    'Programming > Swift' 카테고리의 다른 글

    Swift - Required Initializer  (0) 2021.12.18
    Swift - Failable InitialIzer  (0) 2021.12.17
    Swift - Initializer - Two Phase Initialization  (1) 2021.12.06
    Swift - Initializer Delegation  (0) 2021.11.28
    Swift - Initializer 기초  (1) 2021.11.13
Designed by Tistory.