클래스에서의 생성자는 값 타입에서의 생성자와 조금 다른데,
Memberwise init을 제공하는 값 타입과 달리 클래스는 Memberwise init을 지원하지 않는다.
따라서 모든 클래스들의 프로퍼티는 초기화 과정에서 반드시 초기 값이 할당되어야 한다.
✅ 상속 받은 클래스가 있는 경우 상위 클래스들의 프로퍼티도 모두 초기 값이 할당되어야 한다.
지정 생성자 - Designated Initializer
- 클래스에 지정된 모든 프로퍼티는 초기화 해야함.
- 클래스 타입은 최소 하나 이상의 지정 생성자가 필요함.
init (param) {
// statements
}
편의 생성자 - Convenience Initializer
- 클래스 타입에만 지원하는 보조적인 생성자이다.
- 옵셔널 타입이다.
- 편의 생성자 내부에서 반드시 지정 생성자가 호출되어야 한다. (self.init())
- 꼭 생성되어야 하는 의무는 없다.
convenience init(param) {
self.init()
//statements
}
클래스 타입에서의 생성자 위임
클래스 타입에서의 생성자 위임은 값 타입의 생성자 위임과 조금은 다르다.
✅ 초기화 규칙
1. 지정 생성자는 반드시 슈퍼 클래스의 지정 생성자를 호출해야한다.
2. 편의 생성자는 클래스의 다른 생성자를 호출해야한다.
3. 편의 생성자는 반드시 같은 클래스의 지정 생성자를 호출해야한다. (2번 룰의 연장선이다.)
- why? 지정 생성자는 모든 멤버의 초기화가 보장되므로 다른 편의 생성자를 호출하더라도 최종적으로는 지정 생성자를 호출해야한다.
✅2단계 초기화
1단계 : 자신의 클래스 내부에 있는 저장 프로퍼티에 대한 초기 값이 할당 되어야함.
2단계 : 슈퍼 클래스로부터 상속받은 멤버들을 슈퍼 클래스의 지정 생성자를 호출함으로서 모두 초기화
스위프트의 컴파일러는 2단계 초기화가 에러 없이 완료되는지 확인하기 위해 4가지 check를 한다.
1. 지정 생성자에서 해당 클래스의 저장 프로퍼티를 모두 초기화 해야 슈퍼 클래스의 지정 생성자를 호출할 수 있다.
2. 자식 클래스에서 상속 받은 프로퍼티에 값을 할당하려면 이전에 슈퍼 클래스의 지정 생성자 호출 이후에 할당해야 한다.
3. 편의 생성자에서 해당 클래스의 저장 프로퍼티에 값을 할당하려면 지정 생성자를 호출해야 한다.
4. 인스턴스 메서드나 프로퍼티는 초기화 1단계가 끝나기 전까지 호출할 수 없다.
class Korea {
var city: String
var landmark: String
init() {
self.city = "Seoul"
self.landmark = "Gyeongbokgung"
}
init(city: String, landmark: String) {
self.city = city
self.landmark = landmark
}
}
Korea라는 클래스가 있는데, 해당 클래스의 생성자 모두 convenience 키워드가 붙지 않았기 때문에 지정 생성자이다.
class Seoul: Korea {
let population: Int
init(population: Int) {
self.population = population
super.init()
}
}
✅ 1. 지정 생성자에서 해당 클래스의 저장 프로퍼티를 모두 초기화 해야 슈퍼 클래스의 지정 생성자를 호출할 수 있다.
Korea 클래스를 상속 받은 Seoul 클래스가 있다. Seoul 클래스의 생성자 부분을 보면 super.init()을 호출하기 전에 population을 먼저 초기화 했는데, 만약 population을 초기화 하기 전에 슈퍼 클래스의 지정 생성자를 호출하면 아래와 같이 에러가 발생한다.
따라서 슈퍼 클래스의 지정 생성자를 호출하려면 먼저 자신의 저장 프로퍼티부터 먼저 초기화 해야한다.
✅ 2. 자식 클래스에서 상속 받은 프로퍼티에 값을 할당하려면 이전에 슈퍼 클래스의 지정 생성자 호출 이후에 할당해야 한다.
Seoul 클래스에서 랜드마크를 경복궁에서 63 빌딩으로 바꾸려고 한다.
위와 같이 Seoul 클래스에서 슈퍼 클래스의 지정생성자를 호출하기 전에 상속 받은 landmark 프로퍼티에 값을 할당하려고 하면 에러가 발생한다.
이처럼 자식 클래스에서 상속 받은 프로퍼티에 값을 할당하려면 우선적으로 슈퍼 클래스의 지정 생성자를 호출한 후에 값을 할당해야한다.
✅ 3. 편의 생성자에서 해당 클래스의 저장 프로퍼티에 값을 할당하려면 지정 생성자를 호출해야 한다.
Seoul 클래스에 편의 생성자를 생성하고 편의 생성자에서 Seoul 클래스의 프로퍼티인 population에 접근하려고 하니까 에러가 발생했다.
이는 지정 생성자를 호출하기 전에 해당 클래스의 저장 프로퍼티에 값을 할당하려고 했기 때문이다.
위와 같이 먼저 지정 생성자를 호출하고 해당 클래스의 저장 프로퍼티에 값을 할당하면 에러가 발생하지 않고 정상적으로 작동한다.
✅ 4. 인스턴스 메서드나 프로퍼티는 초기화 1단계가 끝나기 전까지 호출할 수 없다.
서울의 정보를 출력해주는 printSeoul()이라는 메소드를 만들었다. 해당 메소드를 슈퍼 클래스의 지정 생성자를 호출하기 전에 실행하면
아래와 같이 오류가 발생한다.
1단계 초기화 정의를 보면 자신의 클래스 내부에 있는 저장 프로퍼티에 대한 초기 값이 할당 되어야한다. 라고 나와있다.
이는 슈퍼 클래스의 지정 생성자까지 호출되어서 모든 클래스의 프로퍼티가 초기화 되어야지만 1단계가 완료되기 때문에 Seoul 클래스의 printSeoul() 인스턴스 메소드는 슈퍼 클래스의 지정 생성자 호출 이후에 사용할 수 있다.
슈퍼 클래스의 지정 생성자 호출 이후에 Seoul 클래스의 인스턴스 메소드를 호출하면 위와 같이 정상적으로 작동한다.
생성자 오버라이딩
하위 클래스에서 슈퍼 클래스의 지정 생성자를 오버라이딩 할 수 있다.
Korea 클래스에서 만들었던 init()을 하위 클래스인 Seoul 클래스에서 오버라이딩을 통해 재정의하여 생성했다.
✅ 생성자를 오버라이딩 하려면 override 키워드를 이용해서 재정의 하면 된다.
마찬가지로 오버라이딩하여 재정의한 생성자도 슈퍼 클래스의 생성자를 호출하기 전에 가지고 있는 프로퍼티를 초기화 해야한다.
✅ 만약 오버라이드 해야하는 슈퍼 클래스의 생성자가 인자를 받지 않는다면 하위 클래스에서는 오버라이딩하여 재정의한 생성자 내부에 하위 클래스의 프로퍼티만 초기화 해주면 위와 같이 super.init() 호출을 생략할 수 있다.
'Swift > Swift 기본기' 카테고리의 다른 글
[Swift] for-in과 forEach를 알아보자. (1) | 2024.01.10 |
---|---|
14. 생성자 (Initialization) - 4 (0) | 2023.08.02 |
12. 생성자 (Initialization) - 2 (0) | 2023.08.01 |
11. 생성자 (Initialization) - 1 (0) | 2023.08.01 |
10. 제네릭 (Generic) (0) | 2023.07.31 |