초기화(initialization)는 클래스나 구조체 또는 열거형의 인스턴스를 사용하기 위한 준비 과정이다.
이번 장에서는 인스턴스를 생성하는 방법과 클래스의 인스턴스가 소멸할 때 어떤 프로세스가 진행되는지 알아보자.
인스턴스 생성
- 초기화 과정은 새로운 인스턴스를 사용할 준비를 하기 위하여 저장 프로퍼티의 초깃값을 설정하는 등의 일을 한다.
- 이니셜라이저(initializer)를 정의하면 초기화 과정을 직접 구현할 수 있다.
- 스위프트의 이니셜라이저는 반환 값이 없다.
- 이니셜라이저의 역할은 그저 인스턴스의 첫 사용을 위해 초기화하는 것뿐이다.
- 이니셜라이저는 func 키워드를 사용하지 않고 오로지 init 키워드를 사용하여 이니셜라이저 메서드임을 표현한다.
- init 메서드는 클래스, 구조체, 열거형 등의 구현부 또는 해당 타입의 익스텐션 구현부에 위치한다.
- 다만 클래스의 지정 이니셜라이저는 익스텐션에서 구현해줄 수 없다.
프로퍼티 기본값
- 구조체와 클래스의 인스턴스는 처음 생성할 때 옵셔널 저장 프로퍼티를 제외한 모든 저장 프로퍼티에 적절한 초깃값(initial value)을 할당해야 한다.
- 초기화 후에 값이 확정되지 않은 저장 프로퍼티는 존재할 수 없다.
- 프로퍼티를 정의할 때 프로퍼티 기본값(default value)을 할당하면 이니셜라이저에서 따로 초깃값을 할당하지 않더라도 프로퍼티 기본값으로 저장 프로퍼티의 값이 초기화된다.
- 초기화와 프로퍼티 감시자
-
이니셜라이저를 통해 초깃값을 할당하거나, 프로퍼티 기본값을 통해 처음의 저장 프로퍼티가 초기화될 때는 프로퍼티 감시자 메서드가 호출되지 않는다.
class SomeClass {
var value: Int = 0 {
willSet {
print("\(value) will be changed to \(newValue)")
}
didSet {
print("\(oldValue) has been changed to \(value)")
}
}
init() {
self.value = 30
print("property is initialized")
}
}
property is initialized
30
이니셜라이저 매개변수
- 함수나 메서드를 정의할 때와 마찬가지로 이니셜라이저도 매개변수를 가질 수 있다.
옵셔널 프로퍼티 타입
- 초기화 과정에서 값을 초기화하지 않아도 되는, 즉 인스턴스가 사용되는 동안에 값을 꼭 갖지 않아도 되는 저장 프로퍼티가 있다면 해당 프로퍼티를 옵셔널로 선언할 수 있다.
- 옵셔널로 선언한 저장 프로퍼티는 초기화 과정에서 값을 할당해주지 않는다면 자동으로 nil이 할당된다.
상수 프로퍼티
- 상수로 선언된 저장 프로퍼티는 인스턴스를 초기화하는 과정에서만 값을 할당할 수 있으며, 처음 할당된 이후로는 값을 변경할 수 없다.
- *상수 프로퍼티와 상속
- 클래스의 인스턴스의 상수 프로퍼티는 프로퍼티가 정의된 클래스에서만 초기화할 수 있다.
- 해당 클래스를 상속받은 자식클래스의 이니셜라이저에서는 부모클래스의 상수 프로퍼티 값을 초기화할 수 없다.
기본 이니셜라이저와 멤버와이즈 이니셜라이저
- 사용자 정의 이니셜라이저를 정의해주지 않으면 클래스나 구조체는 모든 프로퍼티에 기본값이 지정되어 있다는 전제하에 기본 이니셜라이저를 사용한다.
- 기본 이니셜라이저는 프로퍼티 기본값으로 프로퍼티를 초기화해서 인스턴스를 생성한다.
- 즉, 기본 이니셜라이저는 저장 프로퍼티의 기본값이 모두 지정되어 있고, 동시에 사용자 정의 이니셜라이저가 정의되어 있지 않은 상태에서 제공된다.
- 구조체는 사용자 정의 이니셜라이저를 구현하지 않으면 프로퍼티의 이름으로 매개변수를 갖는 이니셜라이저인 멤버와이즈 이니셜라이저를 기본으로 제공한다.
- 그러나 클래스는 멤버와이즈 이니셜라이저를 지원하지 않는다.
- 구조체에서 사용자 정의 이니셜라이저를 정의하면, 멤버와이즈 이니셜라이저를 사용할 수 없다.
- 사용자 정의 이니셜라이저를 정의할 때도 기본 이니셜라이저나 멤버와이즈 이니셜라이저를 사용하고 싶다면 익스텐션을 사용하여 사용자 정의 이니셜라이저를 구현하면 된다.
초기화 위임
- 구조체와 열거형은 코드의 중복을 피하기 위하여 이니셜라이저가 다른 이니셜라이저에게 일부 초기화를 위임하는 초기화 위임을 간단하게 구현할 수 있다.
- 하지만 클래스는 상속을 지원하는 터라 간단한 초기화 위임도 할 수 없다.
import Foundation
enum Student {
case elementary, middle, high
case none
init() {
self = .none
}
init(koreanAge: Int) {
switch koreanAge {
case 8...13 :
self = .elementary
case 14...16 :
self = .middle
case 17...19 :
self = .high
default :
self = .none
}
}
init(bornAt: Int, currentYear: Int) {
self.init(koreanAge: currentYear - bornAt + 1)
}
}
var younger: Student = Student(koreanAge: 16)
print(younger)
younger = Student(bornAt: 1998, currentYear: 2016)
print(younger)
younger = Student(bornAt: 1990, currentYear: 2021)
print(younger)
실패 가능한 이니셜라이저
- 이니셜라이저를 통해 인스턴스를 초기화할 수 없는 여러 가지 예외 상황이 있다.
- 이니셜라이저를 정의할 때 이런 실패 가능성을 염두에 두기도 하는데, 이렇게 실패 가능성을 내포한 이니셜라이저를 실패 가능한 이니셜라이저(failable initializer)라고 부른다.
- 실패 가능한 이니셜라이저는 클래스, 구조체, 열거형 등에 모두 정의할 수 있다.
- 실패 가능한 이니셜라이저는 실패했을 때 nil을 반환해주므로 반환 타입이 옵셔널로 지정된다.
- 따라서 실패 가능한 이니셜라이저는 init 대신에 init? 키워드를 사용한다.
- 실패 가능한 이니셜라이저는 구조체와 클래스에서도 유용하지만 특히 열거형에서 유용하게 사용할 수 있다.
- 특히 case에 맞지 않는 값이 들어오면 생성에 실패할 수 있다.
함수를 사용한 프로퍼티 기본값 설정 -
- 사용자 정의 연산을 통해 저장 프로퍼티 기본값을 설정하고자 한다면 클로저나 함수를 사용하여 프로퍼티 기본값을 제공할 수 있다.
- 인스턴스를 초기화할 때 함수나 클로저가 호출되면서 연산 결괏값을 프로퍼티 기본값으로 제공해준다.
- 그렇기 때문에 클로저나 함수의 반환 타입은 프로퍼티의 타입과 일치해야 한다.
인스턴스 소멸
- 클래스의 인스턴스는 디이니셜라이저(Deinitializer)를 구현할 수 있다.
- 메모리에서 해제되기 직전 클래스 인스턴스와 관련하여 원하는 정리 작업을 구현할 수 있다.
- 디이니셜라이저는 클래스의 인스턴스가 메모리에서 소멸되기 바로 직전 호출된다.
- deinit 키워드를 사용하여 디이니셜라이저를 구현하면 자동으로 호출된다.
- 디이니셜라이저는 클래스의 인스턴스에만 구현할 수 이 있다.
- 클래스에는 디이니셜라이저를 단 하나만 구현할 수 있다.
- 디이니셜라이저는 이니셜라이저와는 다르게 매개변수를 갖지 않으며, 소괄호도 적어주지 않는다.
- 또, 자동으로 호출되기 때문에 별도의 코드로 호출할 수도 없다.