선행 포스팅
이번 포스팅은 초기화(Initialization)을 실습하는 내용입니다.
Initialization 문서가 너무 길더군요...
문장도 어렵고 내용도 많다보니 최근 포스팅 중 가장 힘들었습니다 ㅎㅎ;
말로만 이해하기엔 붕 뜨는 개념이 있어 실습하면서 이해하기로 했습니다.
let 초기화
저는 지금까지 let은 반드시 선언과 동시에 초기화를 해줘야 한다고 생각했습니다.
근데 init() 안에서 초기화를 해줘도 되더라고요.. 너무 당연히 안 될 거라고 생각했는데
세상에 "당연히"라는 것은 없다는 것을 또 한 번 느꼈습니다.
class Human {
let name: String
init(name: String) {
self.name = name
}
}
var human: Human = Human(name: "애플")
print(human.name) //애플
init() 안에 상수 프로퍼티를 초기화 해주는 것도 가능합니다.
하지만 기본값 설정과 init() 초기화를 동시에 하지는 못해요.
값의 변화가 이루어지는 것이니 let의 개념을 벗어나서 컴파일 에러가 발생합니다.
convenience init
편의 이니셜라이저인 convenience init에 대한 내용도 정확히 알 수 있었습니다.
convenience init은 동일 클래스 내의 init을 호출하여 init 파라미터를 줄이는 방법입니다.
class Human {
let name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
convenience init(name: String) {
self.init(name: name, age: 1)
}
}
Human 클래스는 name과 age를 init()에서 초기화해야 합니다.
하지만 일반적인 상황에서 age가 특정 값(위 예시에서는 1)으로만 사용한다면
convenience init()을 사용해 이니셜라이저를 줄일 수 있습니다.
self.init()을 호출하는 것이 convenience init()의 포인트입니다.
위 convenience init는 신생아가 태어나는 상황을 생각하고 작성해보았습니다 ㅎㅎ
신생아는 항상 1살이니까 인스턴스를 생성할 때 굳이 age를 안 적어도 되죠.
아래 코드는 애플이라는 신생아가 태어난 시나리오입니다.
var human: Human = Human(name: "애플")
print(human.name) //애플
print(human.age) //1
이니셜라이저에 name만 전달하면 convenience init이 호출되어 age가 저절로 1로 설정됩니다.
이렇게 인스턴스를 조금이나마 편하게 생성할 수 있습니다.
Two-Phase Initialization
클래스의 초기화가 두 단계로 이루어진다는 것도 처음 알았습니다.
말로만 보면 너무 어려워서 실습하면서 정확히 이해해보았습니다.
네 가지 safety-check먼저 알아봅시다.
1번 규칙
designated 이니셜라이저는 슈퍼클래스의 이니셜라이저에 delegate 하기 전에 클래스의 모든 속성을 초기화해야 합니다.
객체의 메모리는 모든 저장 프로퍼티가 초기 상태를 갖추어야 완전히 초기화된 것으로 간주합니다.
여기에서 delegate는 이니셜라이저를 호출하는 것을 의미합니다.
즉, 슈퍼클래스의 이니셜라이저를 호출하기 전에 현재 클래스의 모든 프로퍼티를 초기화해야 한다는 의미입니다.
class Korean: Human {
var koreanAge: Int
init() {
koreanAge = 11
super.init(name: "이름", age: 10)
}
}
Human 클래스를 상속하는 Korean 클래스인데요.
init()에서 koreanAge 프로퍼티를 super.init() 전에 초기화해야 합니다.
만약 순서를 반대로 하면 컴파일 에러가 발생해요.
2번 규칙
designated 이니셜라이저는 상속된 프로퍼티를 할당하기 전에 반드시 슈퍼클래스 이니셜라이저로 delegate 해야 합니다.
만약 그렇지 않으면 designated 이니셜라이저가 할당한 새로운 값은 슈퍼클래스의 이니셜라이저에 의해 덮어씌워지게 됩니다.
슈퍼클래스의 이니셜라이저를 호출한 이후에 상속된 프로퍼티를 할당하라는 규칙입니다.
class Korean: Human {
...
init() {
...
super.init(name: "이름", age: 10)
super.name = "시리"
super.age = 20
}
}
var korean: Korean = Korean()
print(korean.name) //시리
print(korean.age) //20
이렇게 super.init을 한 후 super의 프로퍼티를 수정해야 합니다.
만약 순서를 반대로 하게 되면
컴파일 에러가 발생합니다.
3번 규칙
convenience 이니셜라이저는 어떤 프로퍼티의 값을 할당하기 전에 다른 이니셜라이저로 delegate 해야 합니다.
만약 그렇지 않으면 convenience 이니셜라이저가 할당한 새로운 값은 그 클래스의 designated 이니셜라이저에 의해 덮어씌워지게 됩니다.
3번 규칙은 2번 규칙과 유사하므로 패스합니다.
4번 규칙
이니셜라이저는 1단계 초기화가 끝나기 전에 self의 값을 참조하거나 어떤 인스턴스 프로퍼티에 접근하거나 메서드를 호출할 수 없습니다.
말그대로 초기화 전에 값을 참조할 수 없다는 의미입니다.
koreanAge를 초기화 하기 전에 self로 참조하게 되면 컴파일 에러가 발생합니다.
Failable Initializers
실패 가능한 이니셜라이저도 정확한 개념을 알게되었습니다.
사람은 음수 나이가 존재할 수 없습니다.
그래서 이니셜라이저에 age가 음수일 경우엔 초기화 실패를 발생시켰습니다.
class Human {
var name: String
var age: Int
init?(name: String, age: Int) {
if age < 0 { return nil }
self.name = name
self.age = age
}
}
init 옆에 물음표를 붙이고 실패 조건일 때 nil을 반환하면 초기화 실패입니다.
init()에 반환값이 없는데 return을 하는 것이 신기했습니다.
이래서 공식문서에서 return nil을 하긴 하지만 실제로는 nil을 반환하는 것이 아니라는 문장이 있나봅니다.
if let human: Human = Human(name: "애플", age: -1) {
print(human.age)
print(human.name)
} else {
print("초기화 실패")
}
//초기화 실패
age에 음수를 줘서 확인을 해보면 초기화 실패가 출력되는 것을 볼 수 있습니다.
init!(name: String, age: Int) {
if age < 0 { return nil }
self.name = name
self.age = age
}
이렇게 느낌표를 붙이면
초기화 실패시 런타임 에러가 발생합니다.
Failable Initializers for Enumerations with Raw Values
열거형에서 기본으로 실패 가능한 이니셜라이저를 지원한다는 것도 처음 알았습니다.
enum Wheather: String {
case sunny = "맑음", cloudy = "흐림", rainy = "비"
}
if let fahrenheitUnit = Wheather(rawValue: "눈") {
print("fahrenheitUnit: \(fahrenheitUnit)")
} else {
print("열거형 초기화 실패")
}
//열거형 초기화 실패
열거형에서는 init?()을 정의하지 않아도 기본으로 실패 가능한 이니셜라이저를 사용할 수 있습니다.
기억해뒀다 활용하면 매우 유용할 것 같아요.
초기화될 때는 프로퍼티 옵저버 호출 안 됨
init에서 초기화될 때는 프로퍼티 옵저버가 호출이 안 된다는 것도 처음 안 내용입니다.
class Human {
var name: String
var age: Int {
didSet {
print("didSet")
}
willSet {
print("willSet")
}
}
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
age에 didSet과 willSet이 호출되는지 확인해보았습니다.
let human: Human = Human(name: "애플", age: 0)
이렇게 인스턴스 생성을 해도 didSet이나 willSet이 호출이 안 돼요.
initializer에 대한 실습은 여기서 끝입니다.
감사합니다!
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.