[Swift] Initialization
Initialization
초기화
- 초기화는 클래스, 구조체, 열거형의 인스턴스를 생성하는 과정
- 각 저장속성에 대한 초기값을 설정하여 인스턴스를 사용가능한 상태로 만드는 것
(열거형은 저장속성이 존재하지 않으므로, case중에 한가지를 선택 및 생성) - 생성자 메서드 실행의 목적은, 모든 저장속성 초기화를 통한 인스턴스 생성
생성자 실행의 종료시점에는 모든 저장 속성에 값이 저장되어 있어야 함 - 이니셜라이저의 실행이 완료되었을 때, 인스턴스의 모든 저장속성이 초기값을 가지는 것이 생성자의 역할
- 생성자의 반대개념인 소멸자
인스턴스가 해제되기 전에 해야할 기능을 정의하는 부분
- 생성자의 반대개념인 소멸자
class Color {
//let red, green, blue: Double // 동일한 타입일때, 한줄에 작성가능
let red: Double
let green: Double
let blue: Double
// 생성자도 오버로딩(Overloading)을 지원 (파리미터의 수, 아규먼트 레이블, 자료형으로 구분)
// 오버로딩을 통해 파라미터 활용의 자유도가 높아짐
init() { // "init()" -> 기본 생성자. 저장 속성의 기본값을 설정하면 "자동" 구현이 제공됨
red = 0.0
green = 0.0
blue = 0.0
}
init(white: Double) {
red = white
green = white
blue = white
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
var color = Color() // 기본 생성자 호출. 결국 Color()는 생성자를 호출하는 것임 (메서드 호출 문법과 형태 동일)
//var color2 = Color.init()
//color = Color(white: 0.0)
//color = Color(red: 0.0, green: 0.0, blue: 0.0)
- 초기화 방법 (저장속성이 초기값 가져야 함)
- 저장 속성의 선언과 동시에 값을 저장
- 저장 속성을 옵셔널으로 선언 (초기값이 없어도 nil로 초기화 됨)
- 생성자에서 값을 초기화
- 생성자를 구현하지 않으면
- 모든 저장속성에 기본값 (또는 옵셔널 타입) 전제
- 클래스 : 기본 생성자
init()
제공 (초기화 방법 필요) - 구조체 : 기본 생성자
init()
제공 + 멤버와이즈 이니셜라이저 기본 제공
- 클래스 : 기본 생성자
- (일부) 저장속성에 기본값 (또는 옵셔널 타입) 전제
- 클래스 : 원칙적으로 일부 값만 가지고 생성자 구현하지 않는 방법이 존재하지 않음
- 구조체 : 멤버와이즈 이니셜라이저 기본 재공
- 모든 저장속성에 기본값 (또는 옵셔널 타입) 전제
멤버와이즈 이니셜라이저 (memberwise initializer)
- 컴파일러는 기본 생성자를 자동으로 생성함
init()
- 이니셜라이저를 구현하면, 기본 생성자를 자동으로 생성하지 않음
- 구조체는 저장 속성들이 기본값을 가지고 있더라도 추가적으로 멤버와이즈 이니셜라이저를 자동으로 제공
- 개발자가 직접 생성자를 구현하면 멤버와이즈 이니셜라이저가 자동으로 제공되지 않음
생성자
구조체의 생성자
struct Color {
let red, green, blue: Double
init() { // 구조체는 다른 생성자를 호출하는 방식도 가능
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
init(white: Double) {
self.init(red: white, green: white, blue: white)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
// 값타입(구조체)의 경우 자체 지정생성자 작성할 때 생성자 내에서 self.init()을 사용하여 다른 이니셜라이저를 호출하도록 할 수 있음
클래스의 생성자
지정 생성자 (Designated Initializers) & 편의 생성자 (Convenience Initializers)
class Color {
let red, green, blue: Double
// convenience 키워드를 통해 편의 생성자 생성
convenience init() {
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
convenience init(white: Double) {
self.init(red: white, green: white, blue: white)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
// 이미 모든 속성을 초기화하는 지정 생성자가 있다면 모든 속성을 초기화 하지 않는 경우 편의 생성자로 구현 권장
- 편의 생성자는 편리하게 생성하기 위한 메인이 아닌 서브 생성자라고 보면 됨
메인 지정 생성자에 의존하는 방식 (지정 생성자 호출)- 지정 생성자는 모든 속성을 초기화 해야함
- 편의 생성자는 모든 속성을 초기화 할 필요가 없음
- 클래스는 상속을 지원하므로 변수가 여러가지고, 여러 지정 생성자를 지원했을 때 상속관계에서 개발자가 실수할 수 있는 여러가지 가능성이 있음
- 따라서 초기화 과정을 조금 간편하게 만들고, 상속관계에서 개발자가 실수할 수 있는 여러 가능성을 배제하기 위한 생성자
- 반대로 말하면 모든 속성을 초기화 하지 않는다면, 편의 생성자로 만드는 것이 복잡도나 실수를 줄일 수 있는 방안이 될 수 있음
- 결국, 가능한 생성자의 중복을 없애고 다른 지정 생성자를 호출하는 패턴으로 구현해야 함
생성자 위임 규칙 (Initializer Delegation)
모든 저장 속성 값이 초기화 되어야지만 인스턴스 사용 가능
- 델리게이트 업 (Delegate up)
- 서브클래스의 지정 생성자는 슈퍼클래스의 지정 생성자를 반드시 호출해야 함
- 하위 클래스의 저장 속성을 초기화하고 상위 클래스의 저장 속성을 초기화하는 과정
- 델리게이트 어크로스 (Delegate across)
- 편의 생성자는 동일한 클래스에서 다른 편의 생성자 또는 지정 생성자를 호출해야하고 궁극적으로는 지정 생성자를 호출해야 함
- 궁극적으로 지정 생성자만이 해당 단계의 모든 저장값을 초기화 함
-> 인스턴스 메모리 생성에 대한 규칙을 지키지 않으면 인스턴스가 올바르게 초기화 되지 않음
메모리 구조에 따른 상속 관계에서 생성자 호출 과정
- 인스턴스의 모든 저장 속성이 초기값을 가지면 완전히 초기화 된 것
각 단계에서 선언된 저장 속성은 각 해당 단계에서 초기값을 가져야 하고 그러고 나서 슈퍼 클래스로 생성 위임(델리게이트 업)이 일어나야 함
- 해당 클래스에서 선언한 모든 저장 속성에 값이 있는지 확인하여 메모리 초기화
- 상위 지정 생성자로 델리게이트 업 하여 해당 단계의 모든 저장 속성의 초기화
- 최종적으로 기본이 되는 클래스가 모든 저장 속성에 값이 있는지 확인
- 저장 속성이 완전히 초기화 된 것으로 간주되어 인스턴스가 생성 완료
class Person {
var name: String
var email: String
init(name: String, email: String) {
self.name = name
self.email = email
}
}
class Student: Person {
// name
// email
var grade
var gpa
init(grade: Int, gpa: Double) {
self.grade = grade
self.gpa = gpa
}
convenience init() {
self.init(grade: 1, gpa: 0.0)
}
}
- 메모리(코드, 데이터, 힙, 스택)의 데이터 프레임에 슈퍼 클래스(Person)와 서브 클래스(Student) 생성
- 스택 영역에 서브 클래스 스택 프레임 생성
- 스택 영역에 서브 클래스의 편의 생성자
convience init()
스택 프레임 생성 - 델리게이트 어크로스를 통해 서브클래스의 지정 생성자
init()
호출 - 스택 영역에 서브 클래스의 지정 생성자
init()
스택 프레임 생성 - 힙 영역의 빈 메모리 공간에 서브 클래스의 프로퍼티
grade
,gpa
를 포함한 인스턴스 생성 - 서브 클래스의 지정 생성자가 델리게이트 업을 통해 슈퍼 클래스의 지정 생성자 호출
- 스택 프레임에 슈퍼 클래스의 지정 생성자
Super.init()
스택 프레임 생성 - 힙 영역에 생성된 인스턴스에 슈퍼 클래스의 프로퍼티
name
,email
추가 - 스택 영역에서 스택 프레임을 차례대로 제거
Super.init()
->init()
->convience init()
- 마지막으로 스택 영역의 클래스가 힙 스택의 주소를 가리킴
- 이런 동적 할당 과정을 거치게되어 힙 영역을 스캔하는 과정이 포함되어 클래스가 구조체보다 느림
- 스위프트는 하위의 메모리를 먼저 찍어낸 뒤 그 다음으로 상위로 가서 찍어냄 -> 자바와는 반대
생성자의 상속 / 재정의
- 하위 클래스는 기본적으로 상위 클래스 생성자를 상속하지 않고, 재정의 하는 것이 원칙
- 올바르게 초기화되지 않을 수 있는 가능성은 배제 - 하위클래스에 최적화가 안되어 있음
- 상위 클래스와 동일한 모든 지정 생성자가 하위 클래스에 존재할 때 (안전한 경우에) 상위 클래스의 생성자가 자동 상속됨
- 생성자의 재정의
- 동일한 이름을 가진 생성자를 구현하는 것
- 하위 클래스의 커스텀 생성자 구현 전에 상위 클래스의 재정의 생성자를 작성해야 실수하지 않음
생성자 상속 시 구현 규칙
- 기본 원칙
- 상위의 지정 생성자와 현재단계의 저장 속성을 고려해서 구현
- 상위 생성자에 대한 고려
- 상위에 어떤 지정 생성자가 존재하는지
- 상위 지정 생성자
- 하위 클래스에서 지정 생성자로 구현
- 하위 클래스에서 편의 생성자로 구현 가능
- 반드시 재정의하지 않아도 됨
- 상위 편의 생성자
- 호출 불가가 원칙이기 때문에 재정의 제공 하지 않음
(만약 동일한 이름으로 구현했다면 그냥 새로 정의한 개념)
- 호출 불가가 원칙이기 때문에 재정의 제공 하지 않음
- 현재 단계의 생성자 구현
- 지정 생성자 내에서 나의 모든 저장 속성 초기화, 상위 클래스의 지정 생성자 호출
- 편의 생성자 내에서 현재 클래스의 지정 생성자를 호출해야 함
(결론적으로 지정 생성자만 모든 저장 속성을 초기화 가능)
class Aclass {
var x = 0
// init() {} // 기본 생성자가 자동으로 제공
}
class Bclass: Aclass {
var y: Int
// 1. 상위의 지정생성자 고려
// 상위에 동일한 이름이 있으므로 재정의 해야함
// [선택 1] 지정생성자로 재정의
// 상위 클래스와 이름이 동일한 생성자 구현은 재정의만 가능 (지정 생성자로 구현)
override init() {
self.y = 0
super.init()
}
// [선택 2] 하위클래스에서 편의생성자로 구현
// 상위 클래스와 이름이 동일한 생성자 구현은 재정의만 가능 (지정 생성자 필요)
override convenience init() {
self.init(y: 0)
}
// [선택 3] 재정의 하지 않을 수도 있음 (상속안함)
// 2. 현재 단계의 생성자 구현
init(y: Int) {
self.y = y
super.init()
}
}
class Cclass: Bclass {
var z: Int
// 상위 클래스와 이름이 동일한 생성자 구현(올바른 재정의)
override init() {
self.z = 0
super.init() // [예외] 상위 구현의 기본 init() 만 있는 경우 생략가능 (암시적 요청)
}
init(z: Int) {
self.z = z
super.init() // [예외] 상위 구현의 기본 init() 만 있는 경우 생략가능 (암시적 요청)
}
}
[예외] 생성자 자동 상속
기본적으로 초기화의 실패 가능성을 배제 시 자동 상속이 된다.
- 지정 생성자의 자동 상속
- 저장 속성 기본값 설정 및 어떠한 재정의도 하지 않았을 경우
- 편의 생성자의 자동 상속
- 상위 지정 생성자를 모두 상속하는 경우
- 상위의 지정 생성자를 모두 자동 상속하는 경우
- 상위의 지정 생성자를 모두 재정의 하는 경우
-> 결국 모든 지정 생성자를 상속하는 상황이 되면 편의 생성자는 자동으로 상속 됨
- 상위 지정 생성자를 모두 상속하는 경우
필수 생성자 (Required Initializers)
- 클래스의 생성자 앞에
required
키워드를 붙이면 하위 클래스 에서도 반드시 해당 생성자를 구현해야 함 - 하위 클래스에서 필수 생성자 구현 시
override
키워드 없이required
키워드만 붙이면 됨 - 필수 생성자 자동 상속 조건
- 다른 지정 생성자를 구현하지 않으면, 자동으로 필수 생성자가 상속됨
실패 가능 생성자 (Failable Initializers)
- 인스턴스 생성시, 생성에 실패할 수도 있는 가능성을 가진 생성자 (클래스, 구조체, 열거형 모두 가능)
- 실패 가능 생성자를 통해 예외 처리를 할 수 있음
- 인스턴스 생성 실패시 nil을 리턴
- 생성자에 ?를 붙여서
init?()
라고 정의하면 실패 가능 생성자로 정의- 오버로딩으로 실패 가능, 불가능을 구분 지을 수 없으므로, 동일한 파라미터를 가진 생성자는 유일해야함
실패 불가능 생성자는 다른 실패가능 생성자를 호출 불가능
- 동일 단계 (델리케이트 어크로스)
- 실패 가능 생성자 -> 실패 불가능 생성자 (호출/위임 가능)
- 실패 불가능 생성자 -> 실패 가능 생성자 (호출/위임 불가능)
- 상속 관계 (델리케이트 업)
- (상위) 실패 가능 생성자 <- (하위) 실패 불가능 (호출/위임 불가능)
- (상위) 실패 불가능 생성자 <- (하위) 실패 가능 (호출/위임 가능)
두 경우 모두 초기화 실패를 유발하는 다른 생성자에 위임하면 전체 초기화 프로세스가 즉시 실패하고 더 이상 초기화 코드가 실행되지 않음
init!
키워드를 통해 강제 언래핑을 활용하여 호출/위임이 가능하게 만들어 줄 수 있음
소멸자 (Deinitializer)
class Aclass {
var x = 0
deinit { // 소멸자 포맷
print("인스턴스의 소멸 시점")
}
}
- 인스턴스가 메모리에서해제되기 직전 필요한 내용을 구현하는 메서드
- 클래스 정의 시 클래스에는 1개의 소멸자 정의 가능 (한개만 가능)
- 소멸자는 파라미터(매개 변수)를 사용하지 않음
- 소멸자(초기화 해제) 작동 방식
- Swift는 클래스의 인스턴스(객체)를 자동 참조 계산(ARC) 방식을 통해 메모리 관리
- 일반적인 경우(강한 순환 참조를 제외)에는 메모리에서 해제될 때 수동으로 관리를 수행할 필요가 없음
- 인스턴스에서 파일을 열고 일부 데이터를 쓰는 등의 특별한 작업을 수행중인 경우, 몇가지 추가 정리를 위해 직접 수행해야 할 수 있음
- 클래스 인스턴스가 할당 해제되기 전에 파일을 닫아야 파일에 손상이 가지 않음
- 소멸자에서는 인스턴스의 모든 속성에 엑세스 할 수 있으며 해당 속성을 기반으로 동작을 수정할 수 있음
(ex. 닫아야하는 파일의 이름 조회) - 상속이 있는 경우
- 상위 클래스 소멸자는 해당 하위 클래스에 의해 상속됨
- 상위 클래스 소멸자는 하위 클래스 소멸자의 실행이 끝날 때 자동으로 호출됨
- 상위 클래스 소멸자는 하위 클래스가 자체적인 소멸자를 제공하지 않더라도 항상 호출됨
- 소멸자는 클래스에만 존재
- 생성자는 필요한 만큼 구현할 수 있지만 소멸자는 하나만 구현 가능
- 소멸자는 직접 호출 할 수 없고 인스턴스(객체)가 메모리에서 제거되기 직전에 자동으로 호출
구조체 (Struct) | 클래스 (Class) | |
---|---|---|
지정 생성자 (Desinated) | init() {} (생성자 구현 안할 시 기본 생성자 + Memberwise 생성자 기본 제공) init( 파라미터 ) {} | init() {} (생성자 구현 안할 시 기본 생성자 기본 제공) init( 파라미터 ) {} |
편의 생성자 (Convenience) | X | convenience init(파라미터 ) {} (상속과 관련) |
필수 생성자 (Required) | X | required init(파라미터 ) {} (상속과 관련) |
실패 가능 생성자 (Failable) | init?(파라미터 ) {} init!( 파라미터 ) {} | init?(파라미터 ) {} init!( 파라미터 ) {} |
소멸자 (Deinitializers) | X | deinit {} |