[Swift] Initialization(2)

상 원·2022년 7월 21일
0

Swift

목록 보기
24/31
post-thumbnail
post-custom-banner

Class Inheritance and Initialization

클래스의 모~~~든 저장 프로퍼티는 초기화 당시에 값이 할당되어야 함!
슈퍼클래스의 저장 프로퍼티까지 전부 다!

Swift는 그래서 모든 저장 프로퍼티들이 값이 할당되었는지 확인하기 위해서 두 가지 이니셜라이저를 제공하는데, designated initializerconveniene initializer 이다!

Designated Initializers and Convenience Initializers

designated initializer 는 클래스의 기본 이니셜라이저이다.

이놈은 클래스의 모든 프로퍼티를 초기화하고, 상속받은 부모클래스에서 적절한 이니셜라이저를 가져와 실행함.
모든 클래스는 요 이니셜라이저를 최소 하나 가지고 있어야 함! 대부분은 하나씩만 갖고 있다.

convenience initializer 은 secondary initializer임.

이걸로 designated initalizer를 호출해서 일부의 프로퍼티를 초기화할 수 있음.

예를 들어 초기화할 프로퍼티가 name, age 두 개라면
designated init은 name, age 두 개 다 초기화하는 이니셜라이저이고
convenience init은 둘 중 하나만 초기화하는 이니셜라이저임.

Syntax for Designated and Convenience Initializers

클래스의 Designated Initializer는 값 타입의 일반 init이랑 똑같이 쓰면 됨.

init(parameters) {
    statements
}

클래스의 Convenience Initializer는 같은 형식이지만 convenience modifier가 앞에 붙어야 함!

convenience init(parameters) {
    statements
}

Initializer Delegation for Class Types

designated와 convenience의 관계를 간단히 만들기 위한 룰이 있음!

  1. Designated Init은 부모클래스의 Designated Init을 호출해야 함.
  2. Convenience Init은 같은 클래스의 이니셜라이저를 호출해야 함.
  3. Convenience Init은 궁극적으로 Designated Init을 호출해야 함.

대충 이런식으로 돼야 함.
Designated Init은 위로 가야하고, Convenience는 옆으로만!

그니까 Designated Init은 부모의 Designated Init을 호출하고,
Convenience Init은 같은 클래스의 Designated Init을 호출하는 거.

Two-Phase Initialization

Swift에서 클래스 초기화는 크게 두 단계로 나뉨.

  1. 클래스의 모든 저장 프로퍼티 값이 초기화됨.
  2. 다 초기화됐다면 새로운 인스턴스가 사용되기 전에 저장 프로퍼티의 추가 수정 기회가 주어짐.

진짜 뭔말인가..싶지만 좀 더 파헤쳐보면 이해가 되긴 함.


일단 Swift는 요 두 단계의 초기화가 안전하게 이뤄지게 하기 위해서 Safety check를 하는데, 다음 네 개의 룰이 있다.

  • Designated Init은 슈퍼클래스로 올라가기 전에 자기 클래스의 모든 저장 프로퍼티를 초기화해야 함.
    객체에 할당된 메모리는 모든 저장 프로퍼티의 값이 초기화돼야 fully initialized라고 판단함.
  • Designated Init은 슈퍼클래스의 이니셜라이저를 먼저 실행하고 그걸 상속한 프로퍼티의 값을 할당해 줘야 함. delegate한 다음에 값을 할당해줘야 함. 그래야 값을 덮어쓸 수 있음.
  • 초기화의 첫 번째 단계가 끝나기 전에는 이니셜라이저는 인스턴스 메소드를 호출할 수 없고, 인스턴스 프로퍼티의 값을 읽을 수도 없고, self로 접근할 수도 없음!

첫 번째 단계가 끝나기 전까지는 클래스의 인스턴스 초기화가 끝나기 않기 때문에(사용할 수 없기 때문에) 프로퍼티나 메소드의 접근이 불가능함!
요 첫 번째 단계가 끝나고 나서야 메소드 호출이나 프로퍼티 값 접근이 가능하다.

이제 그럼 두 단계를 한번 살펴보자. 이거 보고 나면 이해가 좀 더 쉬움!


1단계

  • Desginated or Convenience Init이 클래스에서 호출됨
  • 해당 클래스의 새 인스턴스에 메모리가 할당됨. 아직 메모리 초기화는 X
  • 해당 클래스의 Designated Init이 모든 저장 프로퍼티를 초기화함. 이제 요 저장 프로퍼티에 대한 메모리는 초기화됨.
  • Designated Init이 해당 클래스의 슈퍼 클래스의 이니셜라이저로 작업을 옮겨줌. 슈퍼클래스의 저장 프로퍼티도 초기화됨.
  • 상속이 끝날 때까지 계~~속 올라가면서 슈퍼클래스의 저장 프로퍼티를 초기화함.
  • Final class에 도달해서 해당 클래스의 Designated Init이 저장 프로퍼티를 다 초기화했다면, 인스턴스의 메모리가 전부 초기화됐다고 판단되고 1단계 종료!

2단계

  • 제일 위 클래스에서 다시 내려오기 시작하면서 각 Designated Init이 인스턴스를 customize할 기회를 줌. 이때부터 각 이니셜라이저들이 self에 접근할 수 있고, 프로퍼티 값을 바꿀 수 있고, 인스턴스 메소드도 호출할 수 있음!
  • 마지막으로 Convenience Init이 self에 접근해서 인스턴스를 customize할 수 있음!

    슈퍼클래스의 Desginted Init부터 다시 내려오면서 인스턴스를 바꿀 수 있음.(바꿀 필요는 없지만)
    그리고 다시 시작이였던 Convenience Init에 도달했을 때 additional customization을 할 수 있다!

와 진짜 어렵고 길다,,, 암튼 클래스 초기화가 이런 두 가지 단계로 이뤄지면서 내가 쓰려고 했던 값을 안전하게 쓸 수 있다는 것!

Initializer Inheritance and Overriding

Swift에서 서브클래스는 슈퍼클래스의 이니셜라이저를 자동으로 상속받지 않음!!
왜냐구? 서브클래스의 인스턴스를 만들랬는데 슈퍼클래스의 이니셜라이저가 실행돼서 잘못된 인스턴스가 생성될 수 있기 때무네!!

만약에 슈퍼클래스 이니셜라이저의 동작을 서브클래스에서도 실행하고 싶다면, 슈퍼클래스의 Designated Init을 서브클래스에서 상속받을 수 있다.
당연히 override를 써서!
게다가 기본으로 생성되는 이니셜라이저도 상속받을 수 있음.

오버라이딩됐기 때문에 Swift가 슈퍼클래스의 이니셜라이저랑 서브클래스에서 오버라이딩한 이니셜라이저가 서로 일치하는지 확인하는 과정도 필요함!

Convenience Init을 상속받고 싶다면?
Conv Init은 바로 상속받을 수 있는 게 아니기 때문에(같은 클래스에서만 쓸 수 있기 때문에) 엄밀하게 말해서 상속받을 수 있다고 말할 수가 없음.
그래서 Conv Init을 상속받으려고 할 때는 override 키워드를 쓰면 안됨!


class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

Vehicle 클래스는 초기화된 저장 프로퍼티가 있고, 그걸 사용하는 연산 프로퍼티가 하나 있음.
default init이 자동으로 생성된 상태!

이걸 상속받는 Bicycle 클래스를 살펴보자.

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)

override initVehicle 의 Default Init을 상속받아 super.init으로 호출함.
호출 후에 customize하고 싶은 것을 적어서 값을 변경한다. 이래야 값을 덮어쓸 수 있음!

만약 서브클래스가 초기화 단계 중 2단계에서 customization을 하지 않고,
슈퍼클래스의 designated init이 매개변수가 없고 synchronus하다면!
서브클래스의 저장 프로퍼티를 모두 초기화한 후 super.init()을 생략할 수 있다.

슈퍼클래스의 designated init이 asynchronus하다면?
await super.init()을 명시해줘야 함!

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() implicitly called here
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver

이런식으로 서브클래스 Hoverboard 가 2페이즈에서 customization을 하지 않으면 super.init은 암시적으로 호출됨!

Automatic Initializer Inheritance

위에서 본 것처럼, 슈퍼클래스의 이니셜라이저는 서브클래스에 자동으로 상속되지 않음.
근데 자동으로 상속되는 두 가지 경우가 있음!!

  • 서브클래스가 Designated Init을 만들지 않았을 때, 슈퍼클래스의 모든 Designated Init을 자동으로 상속받음.
  • 서브클래스가 슈퍼클래스의 모든 Designated Init을 구현했을 때 (위 경우처럼 상속받거나 아예 다 구현했을 때), 슈퍼클래스의 모든 Convenience Init을 상속받음.

서브클래스가 Convenience Init을 정의해놔도 두 경우는 모두 성립함!

이걸로 굳이 override를 하거나 super.init을 호출하지 않아도 된당.

profile
ios developer
post-custom-banner

0개의 댓글