swift init 상속에 대해서 알아봅시다.

swift init 상속에 대해서 알아봅시다.

swift를 처음 접했을 때 init의 상속관련해서 어떤 경우에는 상속이 이루어지고 어떤 경우에는 상속이 되지않아서 이해하기 어려운 부분이 있었습니다. 이번 기회에 init의 상속 관련 내용을 정리하려고 합니다.

먼저, 지정 초기화(designated initializer)와 편의 초기화(convenience initializer)의 관계를 정리하면 3가지 법칙이 있습니다.

  1. 지정 초기화는 부모의 지정 초기화를 호출해야합니다.
  2. 편의 초기화는 부모가 아닌 자신의 초기화를 호출해야합니다.
  3. 편의 초기화는 마지막 지점에서는 지정 초기화를 호출해야합니다.

간단히 다시 정리하면
지정 초기화는 부모의 지정 초기화에게만 위임을 할 수 있으며, 편의 초기화는 자신의 편의 초기화 또는 지정 초기화에게만 위임할 수 있습니다.
아래 이미지로 설명하면 지정 초기화는 위 방향으로만 위임하고 있으며, 편의 초기화는 옆 방향으로 위임하고 있습니다.
enter image description here

아래 이미지는 좀더 복잡한 경우이며 아래의 경우도 위에 설명드린 3가지룰을 지키고 있습니다.
enter image description here

다음으로 알아볼 사항은 property 초기화입니다.
swift는 두 단계 초기화로 이루어져 있습니다. 첫번째 단계에서는 property를 초기화를 수행합니다. 먼저 자신의 property를 모두 초기화하고 그 다음 부모의 property를 초기화합니다. 모든 property를 초기화하고 나면 두번째 단계를 진행합니다. 두번째는 저장된 property를 조작할 기회를 부여합니다. 이때는 자신이 아닌 부모부터 기회를 부여받습니다.

objective-c와 비교하면 swift 초기화 방식이 다르다고 생각할 수 있습니다.
objective-c의 초기화 함수를 보면 부모부터 진행하고 자기 자신의 초기화 코드가 가장 나중에 진행되기 때문입니다. 사실 objective-c의 초기화 단계도 swift의 초기화처럼 두단계로 구성되어 있습니다. 다만, objective-c는 첫번째 단계에서 모든 property를 0이나 nil로 초기화를 하고 두번째 단계에서 각 property값을 구체화하기 때문에 1단계라고 생각한 것 뿐입니다.

swift 컴파일러는 두단계 초기화가 성공적으로 진행되기위해서 4가지의 안정성 점검을 수행합니다.

  1. 안정성 조건1
    지정 초기화는 부모의 지정 초기화에 위임하기 전에 자신의 모든 property를 초기화 해야합니다.

  2. 안정성 조건2
    지정 초기화는 상속받은 property에 접근하기 전에 부모의 초기화에 위임해야 합니다. 그렇게 하지 않은 경우 부모의 초기화에 의해 값이 덮어써지는 문제가 있습니다.

  3. 안정성 조건3
    편의 초기화는 어떤 property든 접근하기 전에 다른 초기화에 위임해야합니다. 그렇지않으면 다른 초기화에 의해 값이 덮어써지는 문제가 있습니다.

  4. 안정성 조건4
    초기화에서는 첫번째 초기화 단계가 완료되기 전에 어떤 property를 읽거나 어떤 instance의 메소드를 호출할 수 없습니다.

위 4단계 조건을 고려하여 초기화 두단계를 다시 정리하면,

  • 1단계
    지정 또는 편의 초기화는 class에서만 호출됩니다.
    새로운 instance의 메모리가 할당되고 그 메모리는 아직 초기화되지 않습니다.
    지정 초기화는 해당클래스의 property에 지정된 값이 있음을 확인합니다. 그 저장 property의 메모리는 초기화됩니다.
    지정 초기화함수는 자신의 property에 대해 동일한 작없을 수행하기 위해 부모의 초기화 함수에 전달됩니다.
    이 작업은 상속 고리의 최상단 부모에 도달할 때까지 계속 진행합니다.
    최상위 부모에 도달하면 모든 property가 초기화되고 instance의 메모리가 완전히 초기화되었다고 할 수있으며 1단계가 완료됩니다.

  • 2단계
    상속고리의 최상위 부모의 지정 초기화함수부터 객체를 구체화할 기회를 부여받습니다. 이번 단계부터 self에 접급할 수 있으며 property수정 및 instance 메소드 호출 등등의 작업이 가능합니다.
    마지막으로 상속 고리의 마지막에 있는 편의 초기화 함수가 instance를 구체화할 기회를 부여받습니다.

마지막으로 알아볼 사항은 지정 및 편의 초기화 함수의 상속 규칙입니다. 이 글을 쓰게된 이유도 이 부분을 명확히 알기 위해서입니다.

  • 규칙1
    자식 클래스에서 부모의 지정 초기화 함수 어떤 것도 정의하지 않을 경우 부모의 지정 초기화 함수를 상속받습니다.
  • 규칙2
    모든 지정 초기화 함수를 구현하거나 규칙1에 의해 지정 초기화 함수를 모두 상속 받은 경우 자동으로 부모의 편의 초기화 함수를 상속받습니다.

Food class를 예로 들어서 설명하겠습니다. 한개의 지정 초기화 함수와 한개의 편의 초기화가 아래와 같이 정의되어 있습니다.

class Food {
    var name: String

    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

그림으로 표현하면 아래 이미지와 같습니다.
enter image description here

RecipeIngredient Class는 Food 클래스를 상속받는 클래스이며 한개의 지정초기화 함수와 재정의한 편의 초기화 함수가 정의되어 있습니다.

class RecipeIngredient: Food {
    var quantity: Int
	
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
	
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

그림으로 표현하면 아래 이미지와 같습니다.
enter image description here
RecipeIngredient Class는 부모의 지정 초기화 함수를 편의 초기화 함수로 정의하고 새로운 지정 초기화 함수를 만들어서 부모의 초기화 함수에 위임하도록 되어 있습니다. 부모의 지정 초기화를 편의 초기화 함수로 재정의하고 있기 때문에 규칙2를 만족하고 있습니다. 따라서, 규칙2에 의해 부모의 편의 초기화 함수 init()을 상속받게 됩니다.

ShoppingListItem class 예를 보겠습니다. RecipeIngredient 클래스를 상속받으며 별도의 초기화 함수는 정의 되어 있지 않습니다.

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
    var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}

그림으로 표현하면 아래 이미지와 같습니다.
enter image description here

ShoppingListItem는 어떤 지정 초기화 함수도 구현하지 않았기 때문에 규칙 1에 의해 부모의 지정 초기화 함수를 상속받습니다. 또한, 규칙2를 만족하기 때문에 부모의 모든 편의 지정 초기화 함수를 상속받습니다. 따라서, ShoppingListItem는 세개의 초기화 함수를 사용할 수 있습니다.

지금까지 Swift의 상속관련 초기화 함수에 대해서 알아보았습니다. 위에 규칙만 잘 숙지하고 있으면 swift에서 초기화가 어떻게 동작하는지 이해할 수 있습니다.

참고사이트: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

댓글

이 블로그의 인기 게시물

dismiss에 대해서 알아봅시다

1. Framework: framework란?

closure에서는 왜 self를 사용해야 할까?