iOS 개발을 하다보면 한번 쯤은 밑과 같은 코드를 사용한다.
클래스에서 이니셜라이저를 override(재정의)하는 코드인데 super.~~~를 쓰지 않으면 에러가 나는 경우가 많았다.
하지만.. 어떤경우에는 super.~~~코드를 빼도 컴파일 에러가 나지 않는 경우도 있었다. 그 이유 에 대해 알아보기 위해
Swift에서 클래스의 초기화가 어떤 방식으로 이루어 지는지 알아보았다.
override init() {
self.property = "property"
super.init()
}
클래스 초기화
Swift에서 클래스들의 초기화는 크게 2단계를 거친다.
- 클래스에 정의한 각각의 저장 프로퍼티에 초깃값이 할당됨.
- 새로운 인스턴스의 사용이 준비됐다고 알려주기 전에 저장프로퍼티들을 커스터마이징 할 수 있는 단계.
이 과정을 거치는 이유?
1. 프로퍼티를 초기화 하기전에 프로퍼티 값에 접근하는 것을 막을 수 있다.
2. 다른 이니셜라이저가 프로퍼티의 값을 실수로 변경하는 것을 방지 할 수있다.
-> 안전하게 클래스를 초기화 하기 위해서임!
2단계 초기화를 안전하게 처리하기 위한 4가지 단계.
1단계, subclass의 지정 이니셜라이저는 클래스 안에서 초기화를 superclass의 이니셜라이저를 호출하기 전에 모든 프로퍼티를 초기화 해야 한다.
메모리에서 객체는 모든 저장된 프로퍼티가 초기 상태를 갖어야만 완전히 초기화 된것으로 간주되기 때문에 지정 초기자는 반드시 다른 초기자로 넘기기 전에 소유하고있는 모든 프로퍼티를 초기화 해야 한다.
그래서, 우리가 항상 자식클래스의 지정 이니셜라이저를 호출하기 전에는 (초깃값이없는)모든 저장 프로퍼티에 값을 초기화 해야했다.
2단계, subclass의 지정 이니셜라이저는 반드시 상속된 값을 할당하기 '전에' superclass의 이니셜라이저로 위임을 넘겨야 한다,
그렇지 않으면 상속된 값이 superclass의 이니셜라이저에 의해 덮어 쓰여지게 된다
3단계, 편의 이니셜라이저는 반드시 어떤 프로퍼티를 할당하기 전에 다른 이니셜라이저로 위임을 넘겨야 한다.
그렇지 않으면 편의 이니셜라이저에 의해 할당된 값을 다른 클래스의 지정 이니셜라이저에 의해 덮어 쓰여지게 된다.
4단계, 이니셜라이저는 초기화의 1단계가 끝나기 전에는 self의 값을 참조하거나 어떤 인스턴스 프로퍼티, 메소드 등을 호출하거나 읽을 수 없다.
2단계 초기화가 이루어지는 과정
글로만 봐서는 잘 모르겠으니 Swift Language Guide에 나와있는 그림과 함께 살펴보자.
1단계
1. 클래스가 지정 또는 편의 이니셜라이저를 호출.
2. 클래스의 새로운 인스턴스를 위한 메모리가 할당(아직 초기화는 되지 않은 상태)
3. 지정 이니셜라이저는 클래스에 정의된 모든 저장 프로퍼티에 값이 있는지 확인
(현재클래스 부분까지 저장프로퍼티를 위한 메모리는 이제 초기화됨)
4. 지정 이니셜라이저는 부모클래스의 이니셜라이저가 또같이 초기화 할 수 있도록 초기화를 양도함.
5. 상속체인을 따라 최상위 클래스에 도달할 때 까지 작업을 반복.
Swift에서 클래스는.. 자식클래스부터 최상층의 부모클래스까지 초기화를 계속 양도하면서 1단계 초기화를 진행함을 알 수 있음!
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 {
let school: String
override init(name: String, age: Int) {
self.school = "한국대학교"
super.init(name: name, age: age)
}
}
self.school 코드 부분을 super.init 밑에 두게 되면 에러가 나는 이유를 알 수 있게 되었다!
-> 초기화 1단계에서 subclass를 초기화 한 뒤에 superClass로 가야하는데 아직 모든 값이 초기화 되지않았기 때문!!
이대로 끝내면 아쉬우니 상속과 재정의의 예제를 조금더 살펴본다.
이니셜라이저 상속과 재정의(Overriding)
Swift에서는 기본적으로 subclass에서 superclass의 이니셜라이저를 상속하지 않는다.
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
이니셜라이저 자동 상속
subclass는 superclass의 초기자를 기본적으로 상속하지 않는다.
사실 많은 상황에서 직접 초기자를 오버라이드 할 필요가 없다.
하지만 특정 상황에서 자동으로 상속 받는다.
서브클래스에서 새로 추가한 모든 프로퍼티에 기본 값을 제공 한다면 두가지 규칙에 따라 자동 상속된다.
규칙1 서브클래스가 지정 이니셜라이저를 정의하지 않을 경우.
규칙2 서브클래스가 수퍼클래스의 지정 이니셜라이저를 모두 구현한 경우.
지정 이니셜라이저와 편의 이니셜라이저의 사용 예제.
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
Food를 서브클래싱한 RecipeIngredient클래스에서 편의 이니셜라이저를 override한 예제.
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
참고
https://jusung.gitbook.io/the-swift-language-guide/language-guide/10-properties
스위프트 프로그래밍 - 야곰
'Swift' 카테고리의 다른 글
Task Group (0) | 2023.01.14 |
---|---|
Swift Concurrency 알아보기 (0) | 2023.01.08 |
DiffableDataSource 알아보기. (7) | 2022.07.25 |
정규 표현식 in Swift (2) | 2022.06.14 |
Type Erasure in Swift (0) | 2022.05.23 |
댓글