Swift - 클래스의 초기화 위임


Swift에서 클래스의 초기화 위임은 어떻게 아루어지는 것일까?


초기화 위임이 어떻게 이루어지는지 알기 전에 이니셜라이저에 대한 이해가 있어야 한다.

지정 이니셜라이저와 편의 이니셜라이저

Designed Initializer(지정 이니셜라이저)는 클래스의 주요 이니셜라이저다. 필요에 따라 부모클래스의 이니셜라이저를 호출할 수 있고, 이니셜라이저가 정의된 클래스의 모든 프로퍼티를 초기화해야 한다. 지정 이니셜라이저는 클래스의 이니셜라이저 중 가장 기본이 되는 이니셜라이저로 클래스에 하나 이상 정의한다. 이를 다르게 말하자면 모든 클래스는 하나 이상의 지정 이니셜라이저를 갖는다는 말이 된다. 만약 조상클래스에서 지정 이니셜라이저가 자손클래스의 지정 이니셜라이저 역할을 할 수 있으면, 자손클래스는 지정 이니셜라이저를 갖지 않을 수 있다. 이런 경우는 조상클래스로부터 물려받은 프로퍼티를 제외하고 옵셔널 저장 프로퍼티 외에 다른 저장 프로퍼티가 없을 가능성이 크다.(옵셔널 저장 프로퍼티는 이니셜라이저에서 초기화해줄 필요가 없으므로)

Convenience Initializer(편의 이니셜라이저) 는 초기화를 더 쉽게 도와주는 역할이다. 편의 이니셜라이저는 지정 이니셜라이저를 자신 내부에서 호출한다. 지정 이니셜라이저의 매개변수가 많아 외부에서 일일이 전달인자를 전달하기 어렵거나 특정 목적을 위해 편의 이니셜라이저를 설계한다.

// 지정 이니셜라이저
init(매개변수들) {
  초기화 구문
}

// 편의 이니셜라이저
convenience init(매개변수들) {
  초기화 구문
}

클래스의 초기화 위임

지정 이니셜라이저와 편의 이니셜라이저의 관계는 세 가지 규칙을 적용할 수 있다.

  1. 자식클래스의 지정 이니셜라이저는 부모클래스의 지정 이니셜라이저를 무조건 호출해야 한다.
  2. 편의 이니셜라이저는 자신을 정의한 클래스의 다른 이니셜라이저를 무조건 호출해야 한다.
  3. 편의 이니셜라이저는 궁극적으로는 지정 이니셜라이저를 무조건 호출해야 한다.

즉 편의 이니셜라이저 B가 편의 이니셜라이저 A를 호출했다 하더라도 A가 지정 이니셜라이저를 호출하고 있으면 되는 것이다.

2단계 초기화(Two-Phase Initialization)

스위프트의 클래스 초기화는 두 단계를 거친다.

  • 1단계 : 클래스에 정의한 각각의 저장 프로퍼티에 초깃값이 할당된다.
  • 2단계 : 초깃값(초기 상태)이 정해지면 저장 프로퍼티들을 사용자 정의할 수 있다.

참고로 Swift의 2단계 초기화는 Objective-C에서의 초기화와 유사하지만, 첫 번째 단계에서 차이가 있다. Obj-c에서는 모든 프로퍼티에 zero 혹은 null을 할당하지만, Swift의 초기화는 좀 더 유연해서 커스텀한 초기 값을 할당할 수 있다.

이 2단계 초기화는 프로퍼티를 초기화하기 전에 프로퍼티 값에 접근하는 것을 막아 초기화를 안전하게 할 수 있게 해주고, 다른 이니셜라이저가 프로퍼티 값을 실수로 변경하는 것을 방지한다.

스위프트 컴파일러는 2단계 초기화를 오류 없이 처리하기 위해 네 가지 안전확인(safety checks)을 실행한다.

  1. 자식클래스의 지정 이니셜라이저가 부모클래스의 지정 이니셜라이저를 호출하기 전에 자신의 프로퍼티를 모두 초기화했는지 확인한다. (자식 클래스의 지정 이니셜라이저에서 부모클래스의 이니셜라이저를 호출하기 전에 자신의 모든(기본값이 없는) 저장 프로퍼티에 값을 할당해야 한다.) 메모리에서 객체는 모든 저장된 프로퍼티가 초기 상태를 가져야만 완전히 초기화 된 것으로 간주하기 때문에 이 규칙을 만족시키기 위해 지정이니셜라이저는 반드시 다른 이니셜라이저로 넘기기 전에 소유하고 있는 모든 프로퍼티를 초기화 해야 한다.
  2. 자식 클래스의 지정 이니셜라이저는 상속받은 프로퍼티에 값을 할당하기 전에 반드시 부모클래스의 이니셜라이저를 호출해야 한다. 그렇지 않으면 상속된 값이 부모의 이니셜라이저에 의해 덮어써지게 된다.
  3. 편의 이니셜라이저는 자신의 클래스에 정의한 프로퍼티를 포함하여 그 어떤 프로퍼티라도 값을 할당하기 전에 다른 이니셜라이저를 호출해야 한다. 그렇지 않으면 편의 이니셜라이저에 의해 할당된 값을 다른 클래스의 지정 이니셜라이저에 의해 덮어 써지게 된다.
  4. 초기화 1단계를 마치기 전까지는 self의 값을 참조하거나 어떤 인스턴스 프로퍼티, 메소드 등을 호출하거나 읽을 수 없다.

클래스의 인스턴스는 초기화 1단계를 마쳤을 때 비로소 유효한 인스턴스가 된다고 생각하면 된다. 이제 네 가지 안전확인에 근거해서 2단계 초기화가 어떻게 이루어지는지 보겠다.

1단계

  1. 클래스가 지정/편의 이니셜라이저를 호출한다.
  2. 클래스의 새로운 인스턴스를 위한 메모리가 할당되기만 하고, 초기화 되지는 않은 상태다.
  3. 지정 이니셜라이저는 클래스에 정의된 모든 저장 프로퍼티에 값이 있는지 확인한다. 이제 이니셜라이저를 호출한 클래스까지의 저장 프로퍼티를 위한 메모리가 초기화되었다고 하자.
  4. 지정 이니셜라이저는 부모클래스의 이니셜라이저가 같은 동작을 행할 수 있도록 초기화를 양도한다.
  5. 부모클래스는 상속 체인을 따라 최상위 클래스에 도달할 때까지 작업을 반복한다.

최상위 클래스에 도달했을 때, 치ㅗ상위 클래스까지의 모든 저장 프로퍼티에 값이 있다고 확인하면 해당 인스턴스의 메모리는 모두 초기화된 것이다.

2단계

  1. 최상위 클래스로부터 최하위 클래스까지 상속 체인을 따라 내려오면서 지정 이니셜라이저들이 인스턴스를 제각각 사용자 정의하게 된다. self를 통해 프로퍼티 값을 수정할 수 있고, 인스턴스 메서드를 호출할수도 있다.
  2. 각각의 편의 이니셜라이저를 통해 selffmf xhdgks tkdydwk wjddml wkrdjqdmf wlsgodgkf tn dlTek.

출처

  • Swift 프로그래밍 : 야곰님 저
  • https://jusung.gitbook.io/the-swift-language-guide/language-guide/14-initialization