상속-2

피터·2022년 8월 26일
0
post-thumbnail

클래스의 이니셜라이저 - 상속과 재정의

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

  • 지정 이니셜라이저(Designated Initializer)
    • 주요 이니셜라이저

    • 필요에 따라 부모클래스의 이니셜라이저를 호출할 수 있습니다.

    • 이니셜라이저가 정의된 클래스의 모든 프로퍼티를 초기화해야 함

    • 조상클래스의 지정 이니셜라이저가 자손클래스의 지정 이니셜라이저 역할을 한다면 자손클래스의 지정 이니셜라이저가 없을 수도 있음
      → 하지만 그렇기 위해서는 자손 클래스는 옵셔널 저장 프로퍼티 외의 저장 프로퍼티는 가질 수 없을 것

      init(매개변수) {
          초기화 구문 
      }
  • 편의 이니셜라이저(Convenience Initializer)
    • 편의 이니셜라이저는 초기화를 좀 더 손쉽게 도와주는 역할

    • 편의 이니셜라이저는 지정 이니셜라이저를 자신 내부에서 호출 함

      • 자동으로 호출하는 것인가 호출해야만 하는 것인가? → 호출해야만 한다.
    • 클래스 설계자의 의도대로 외부에서 사용하길 원하거나 인스턴스 생성 코드를 작성하는 수고를 덜 때 유용하게 사용

      convenience init(매개변수) {
          초기화 구문
      }

클래스의 초기화 위임

지정 이니셜라이저와 편의 이니셜라이저 사이의 관계에서 3가지 규칙이 있습니다.

  1. 자식클래스의 지정 이니셜라이저는 부모클래스의 지정 이니셜라이저를 반드시 호출해야 합니다.
    1. 자식클래스의 지정 이니셜라이저가 여러 개라고 하더라도 무조건 부모 클래스의 지정 이니셜라이저를 호출해야 합니다.
  2. 편의 이니셜라이저는 자신을 정의한(속한) 클래스의 다른 이니셜라이저를 반드시 호출해야 합니다.
    1. 상속을 받았다고 하더라도 부모클래스의 이니셜라이저를 호출할 수는 없습니다.
      무조건 자기 사진이 속한 클래스의 이니셜라이저만 호출 할 수 있으며 반드시 호출해야 합니다.
      그리고 그 끝은 항상 지정 이니셜라이저를 호출해야합니다.
  3. 편의 이니셜라이저는 궁극적으로는 지정 이니셜라이저를 반드시 호출해야 합니다.

2단계 초기화

스위프트의 클래스 초기화는 2단계를 거칩니다.

  • 1단계에서는 클래스에 정의한 각각의 저장프로퍼티에 초깃값이 할당됩니다.
  • 2단계에서는 프로퍼티를 초기화하기 전에 프로퍼티 값에 접근하는 것을 막아 초기화를 안전하게 할 수 있도록 해줍니다. 또, 다른 이니셜라이저가 프로퍼티의 값을 실수로 변경하는 것을 방지할 수도 있습니다.
  • 스위프트 컴파일러의 4가지 안전확인(Safety-checks) 스위프트 컴파일러는 2단계 초기화를 오류 없이 처리하기 위해 다음과 같은 4가지 안전확인(Safety-checks)을 실행합니다.
    1. 자식클래스의 지정 이니셜라이저가 부모클래스의 이니셜라이저를 호출하기 전에 자신의 프로퍼티를 모두 초기화했는지 확인합니다.
      1. 위 규칙때문에 자식클래스의 지정 이니셜라이저에서 부모클래스의 이니셜라이저를 호출하기 전에 자신의 모든 (기본값이 없는) 저장 프로퍼티에 값을 할당해주어야 합니다.
    2. 자식클래스의 지정 이니셜라이저는 상속받은 프로퍼티(부모의 것)에 값을 할당하기 전에 반드시 부모클래스의 이니셜라이저를 호출해야 합니다.
    3. 편의 이니셜라이저는 자신의 클래스에 정의한 프로퍼티를 포함하여 그 어떤 프로퍼티라도 값을 할당하기 전에 다른 이니셜라이저를 호출해야 합니다.
    4. 초기화 1단계를 마치기 전까지는 이니셜라이저는 인스턴스 메서드를 호출할 수 없습니다. 또, 인스턴스 프로퍼티의 값을 읽어들일 수도 없습니다. self 프로퍼티를 자신의 인스턴스를 나타내는 값으로 활용할 수도 없습니다.

클래스의 인스턴스는 초기화 1단계를 마치기 전까지는 아직 유효하지 않습니다. 클래스의 인스턴스가 초기화 1단계를 마쳤을 때 비로소 유효한 인스턴스가 되는 것입니다.

위 네 가지 안전확인에 근거하여 어떻게 2단계 초기화가 이루어지는지 살펴보겠습니다.

1단계

  1. 클래스가 지정 또는 편의 이니셜라이저를 호출합니다.
  2. 그 클래스의 새로운 인스턴스를 위한 메모리가 할당됩니다. 메모리는 아직 초기화되지 않은 상태입니다.
  3. 지정 이니셜라이저는 클래스에 정의된 모든 저장 프로퍼티에 값이 잇는지 확인합니다. 현재 클래스 부분까지의 저장 프로퍼티를 위한 메모리는 이제 초기화되었습니다.
  4. 지정 이니셜라이저는 부모클래스의 이니셜라이저가 같은 동작을 행할 수 있도록 초기화를 양도합니다.
  5. 부모클래스는 상속 체인에 다라 최상위 클래스에 도달할 때까지 이 작업을 반복합니다.

2단계

  1. 최상위 클래스로부터 최하위 클래스까지 상속 체인을 따라 내려오면서 지정 이니셜라이저들이 인스턴스를 제 각각 사용자 정의하게 됩니다. 이 단계에서는 self를 통해 프로퍼티 값을 수정할 수 있고, 인스턴스 메서드를 호출하는 등의 작업을 진행할 수 있습니다.
  2. 마지막으로 각각의 편의 이니셜라이저를 통해 self를 통한 사용자 정의 작업을 진행할 수 있습니다.
class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
class Student: Person {
    var major: String
    
    init(name: String, age: Int, major: String) {
        self.major = major
        super.init(name: name, age: age)
    }
    
    convenience init(major: String) {
        self.init(name: "피터", age: 30, major: major)
    }
}
  • Student 지정 이니셜라이저에서 Student 클래스의 프로퍼티인 major는 부모 클래스의 초기화보다 먼저 설정되어야 한다.

이니셜라이저 상속 및 재정의

기본적으로는 스위프트의 이니셜라이저는 부모클래스의 이니셜라이저를 상속받지 않습니다. 부모클래스의 이니셜라이저는 자식클래스에 최적화되어 있지 않아서, 부모클래스의 이니셜라이저를 사용했을 때 자식클래스의 새로운 인스턴스가 완전하고 정확하게 초기화되지 않는 상황을 방지하고자 합니다.

단, 예외적으로 특정한 상황에서는 부모클래스의 이니셜라이저가 상속되기도 합니다. (이니셜라이저 자동 상속)

부모클래스의 이니셜라이저 사용하기

  • 지정 이니셜라이저 부모클래스의 이니셜라이저를 사용하기 위해서는 해당 이니셜라이저를 자식클래스에서 override 수식어를 붙여서 재정의하면 됩니다.
  • 편의 이니셜라이저
    • 자식클래스에서는 절대로 구현할 수 없습니다.
  • 실패 가능한 이니셜라이저
    • 실패 가능한 이니셜라이저로 재정의해도 되고 필요에 따라서 실패하지 않는 이니셜라이저로 재정의해줄 수도 있습니다.

이니셜라이저 자동 상속

기본적으로 스위프트의 이니셜라이저는 부모클래스의 이니셜라이저를 상속받지 않지만 특정 조건에 부합한다면 부모클래스의 이니셜라이저가 자동으로 상속됩니다.

🔔 특정 조건이란?

자식클래스에서 프로퍼티 기본값을 모두 제공한다고 가정할 때,

규칙 1. 자식클래스에서 별도의 지정 이니셜라이저를 구현하지 않는다면, 부모클래스의 지정 이니셜라이저가 자동으로 상속됩니다.

규칙 2. 만약 규칙 1.에 따라 저식클래스에서 부모클래스의 지정 이니셜라이저를 자동으로 상속받은 경우 또는 부모클래스의 지정 이니셜라이저를 모두 재정의하여 부모클래스와 동일한 지정 이니셜라이저를 모두 사용할 수 있는 상황이라면 부모클래스의 편의 이니셜라이저가 모두 자동으로 상속됩니다.

자동 상속 규칙은 자식클래스의 편의 이니셜라이저를 추가한다고 하더라도 유효합니다.

또, 부모클래스의 지정 이니셜라이저를 자식클래스의 이니셜라이저르 구현하더라도 규칙 2.를 충족합니다.

class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "알 수 없음")
    }
}

class Student: Person {
    var major: String
    
		// major의 기본값이 없지만 이니셜라이저에서 적절히 초기화
    init(name: String, major: String) {
        self.major = major
        super.init(name: name)
    }
    
		// 부모클래스의 지정 이니셜라이저를 모두 재정의
		// -> 부모클래스와 지정 이니셜라이저와 동일한 이니셜라이저를 모두 사용 가능
		// -> 규칙1에 의하여 부모클래스의 편의 이니셜라이저가 자동으로 상속됨
    override init(name: String) {
        self.major = "알 수 없음"
        super.init(name: name)
    }
}

let person: Person = Person.init()
// Student에는 없는 이니셜라이저, 부모클래스의 편의 이니셜라이저 자동 상속(규칙 2)
let student: Student = Student.init()
print(person.name)
print(student.name)

// 결과 
알 수 없음
알 수 없음

위의 코드에서 Student에 편의 이니셜라이저를 추가하면 ?

import Foundation

class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "알 수 없음")
    }
}

class Student: Person {
    var major: String
    
    init(name: String, major: String) {
        self.major = major
        super.init(name: name)
    }
    
    override init(name: String) {
        self.major = "알 수 없음"
        super.init(name: name)
    }
    
    convenience init(major: String) {
        self.init()
        self.major = major
    }
}

let person: Person = Person.init()
let student: Student = Student.init(major: "이러쿵 저러쿵")
print(person.name)
print(person.name)
print(student.name)

규칙 2에 따라서 편의 이니셜라이저는 자동 상속에는 아무런 영향을 미치지 않았습니다.

요구 이니셜라이저

required 수식어를 클래스의 이니셜라이저 앞에 명시해주면 이 클래스를 상속받은 자식클래스에서 반드시 해당 이니셜라이저를 구현해주어야 합니다.

required init() {
    
}

만약 상속받은 클래스에서 별도의 지정 이니셜라이저가 없다면 요구 이니셜라이저는 자동 상속되지만 그렇지 않은 경우에는 요구 이니셜라이저를 재정의 해주어야 합니다.

required override init() {
    
}

자료 출처: 야곰 스위프트 프로그래밍 3판

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
profile
iOS 개발자입니다.

0개의 댓글