클래스의 모~~~든 저장 프로퍼티는 초기화 당시에 값이 할당되어야 함!
슈퍼클래스의 저장 프로퍼티까지 전부 다!
Swift는 그래서 모든 저장 프로퍼티들이 값이 할당되었는지 확인하기 위해서 두 가지 이니셜라이저를 제공하는데, designated initializer
와 conveniene initializer
이다!
designated initializer 는 클래스의 기본 이니셜라이저이다.
이놈은 클래스의 모든 프로퍼티를 초기화하고, 상속받은 부모클래스에서 적절한 이니셜라이저를 가져와 실행함.
모든 클래스는 요 이니셜라이저를 최소 하나 가지고 있어야 함! 대부분은 하나씩만 갖고 있다.
convenience initializer 은 secondary initializer임.
이걸로 designated initalizer를 호출해서 일부의 프로퍼티를 초기화할 수 있음.
예를 들어 초기화할 프로퍼티가 name, age 두 개라면
designated init은 name, age 두 개 다 초기화하는 이니셜라이저이고
convenience init은 둘 중 하나만 초기화하는 이니셜라이저임.
클래스의 Designated Initializer는 값 타입의 일반 init이랑 똑같이 쓰면 됨.
init(parameters) { statements }
클래스의 Convenience Initializer는 같은 형식이지만 convenience modifier가 앞에 붙어야 함!
convenience init(parameters) { statements }
designated와 convenience의 관계를 간단히 만들기 위한 룰이 있음!
대충 이런식으로 돼야 함.
Designated Init은 위로 가야하고, Convenience는 옆으로만!
그니까 Designated Init은 부모의 Designated Init을 호출하고,
Convenience Init은 같은 클래스의 Designated Init을 호출하는 거.
Swift에서 클래스 초기화는 크게 두 단계로 나뉨.
진짜 뭔말인가..싶지만 좀 더 파헤쳐보면 이해가 되긴 함.
일단 Swift는 요 두 단계의 초기화가 안전하게 이뤄지게 하기 위해서 Safety check
를 하는데, 다음 네 개의 룰이 있다.
첫 번째 단계가 끝나기 전까지는 클래스의 인스턴스 초기화가 끝나기 않기 때문에(사용할 수 없기 때문에) 프로퍼티나 메소드의 접근이 불가능함!
요 첫 번째 단계가 끝나고 나서야 메소드 호출이나 프로퍼티 값 접근이 가능하다.
이제 그럼 두 단계를 한번 살펴보자. 이거 보고 나면 이해가 좀 더 쉬움!
와 진짜 어렵고 길다,,, 암튼 클래스 초기화가 이런 두 가지 단계로 이뤄지면서 내가 쓰려고 했던 값을 안전하게 쓸 수 있다는 것!
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 init
에 Vehicle
의 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은 암시적으로 호출됨!
위에서 본 것처럼, 슈퍼클래스의 이니셜라이저는 서브클래스에 자동으로 상속되지 않음.
근데 자동으로 상속되는 두 가지 경우가 있음!!
서브클래스가 Convenience Init을 정의해놔도 두 경우는 모두 성립함!
이걸로 굳이 override를 하거나 super.init을 호출하지 않아도 된당.