티스토리 뷰
INDEX
1. 지정 생성자(Designated Initializer)
2. 편의 생성자(Convenience Initializer)
3. 생성자 위임 규칙(Initialization Delegation)
4. 생성자 자동 상속(Automatic Initializer Inheritance) - 예외사항
5. 마무리
이니셜라이져(Initializer)를 줄여서 생성자로 불렀습니다.
1. 지정 생성자(Designated Initializer)
지정생성자(Designated Initializers)
클래스내에 있는 모든 속성들을 초기화하는 생성자
생성자 사용시 주의점❗️
해당 생성자가 종료되기 전까지, 생성자 안의 모든 속성들의 초기화가 이루어져야한다.
상속을 받는다면, 반드시! super클래스의 생성자를 현단계의 생성자에서 호출해줘야한다. why?
상위 클래스의 속성의 초기화가 이루어지지 않았을 수도 있기 때문
//🍎공식문서 예제
class SurveyQuestion {
var text: String
var response: String? //옵셔널 타입이므로 nil값 자동 초기화
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
⭐️상속 관계에서의 지정 생성자⭐️
클래스에서 꼭 1개 이상은 정의 되어야 하는 핵심 생성자로, 모든 속성(프로퍼티)들을 초기화해야하는 의무를 갖고 있다.
class Aclass {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
class Bclass: Aclass {
var z: Int
//지정 생성자 구현하기
}
⭐️상속관계에서 지정생성자의 법칙⭐️
상속관계에서 지정생성자의 법칙은 간단하다.
2가지 법칙만 정확하게 기억하고 코드에 적용하면 된다. (이게 혼동되면 어려워진다...)
1. 현재 클래스 단계에서 선언한 초기화 되지 않은 저장속성의 값을 셋팅한다.
2. 상위 클래스 단계의 속성 값은 상위클래스에 super키워드를 통하여 위임한다.
이 2가지만 지키면 지정생성자의 구현이 쉬워지고 다양한 형태로도 만들 수 있다.
상위 지정생성자를 재정의하는 override init()
곧 이어 소개할 convenience init()도 이 2가지 법칙만 기억하면 쉽게 이해할 수 있다.
아래코드는 2가지 지정생성자를 만들었다.
상위클래스에 지정생성자가 Bclass처럼 여러개(init, override init)라면 하위클래스에서 모두 구현해 줘야 할까?
아니다. 그 중 1개만 호출해주면된다. 위 2가지 법칙만 떠올리면된다.
현단계의 저장속성의 값을 셋팅하고, 상위클래스의 저장속성 값셋팅 위임의 역할을 할 수 있다면 상관없다.
앞포스팅에서도 했지만, 구조체에서도 위와 같이 여러 선택지를 파라미터로 갖는 초기화가 가능하다.
//🍎공식문서 예제
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
2. 편의 생성자(Convenience Initializer)
편의생성자(Convenience Initializer)
편의 생성자는 지정생성자를 보조해주기 위해 나타났으며, 초기화를 간편히 할 수 있게 도와준다.(말그대로 편의상 사용)
편의 생성자는 직접 메모리값을 셋팅할 수는 없기 때문에 지정생성자에게 위임하는 역할을 한다. (결국에는 지정생성자를 거쳐야한다)
편의 생성자는 필수요소는 아니며, 클래스 설계자가 클래스를 사용할때 수고를 덜게끔하기위해 유용하게 사용가능하다.
편의 생성자는 상위클래스의 지정생성자를 재정의하여 편의 생성자로 사용할 수도 있다.
(헷갈릴 수 있는 부분이 있다면 이부분. 처음 보면 헷갈릴수도 있다. 결국 기존의 2가지 법칙만 기억하고 편의 생성자는 지정생성자 없이는 아무것도 못한다는걸 기억하자.아래코드의 마지막에 배치해 두었다.)
class Aclass {
var x: Int
var y: Int
init(x: Int, y: Int) { // 지정생성자 - 모든 저장 속성 설정
self.x = x
self.y = y
}
convenience init() { // 편의생성자 - (조금 편리하게 생성) 모든 저장 속성을 설정하지 않음
self.init(x: 1, y: 1)
}
}
var t = Aclass() //편의생성자 호출
t.x //1
t.y //1
//상속이 일어나는 경우 ⭐️
class Bclass: Aclass {
var z: Int
//1. 지정생성자
init(x: Int, y: Int, z: Int) { // 실제 메모리에 초기화 되는 시점
self.z = z // ⭐️ (필수)
//self.y = y // 불가 (메모리 셋팅 전)
super.init(x: x, y: y) // ⭐️ (필수) 상위의 지정생성자 호출
//self.z = 7
//self.y = 7
//self.doSomething()
}
//2. 편의생성자 3개 (오버로딩 방식)
convenience init(z: Int) {
//self.z = 7 //==========> self에 접근불가함
self.init(x: 2, y: 2, z: z) //위의 지정생성자 호출
}
//동일한 클래스의 다른 생성자 호출가능
convenience init() {
self.init(z: 0) //위의 편의 생성자 호출
}
//상위클래스의 지정생성자를 편의생성자로 재정의 가능! => 결국 모든(상위와 현단계의) 저장속성 값 셋팅이 가능하기 때문!
override convenience init(x: Int, y: Int) {
self.init(x: x, y: y, z: 8)
}
}
let a = Aclass(x: 1, y: 1)
let a1 = Aclass()
let b = Bclass(x: 1, y: 1, z: 1)
let b1 = Bclass(z: 2)
b1.x //2
let b2 = Bclass()
let b3 = Bclass(x: 3, y: 5)
b3.x //3
b3.z //8
위의 예시코드는 편의생성자로 구현가능한 대부분의 패턴을 다루었다고 볼 수 있다.
(추가적인 편의생성자 정의도 가능하나, 필요에 맞게끔 정의하면 된다.)
그렇다면 편의생성자는 지정생성자와는 어떤 차이가 있을까?
이를 설명한 것이 아래의 생성자 위임 규칙이다.
3. ⭐️ 생성자 위임 규칙(Initialization Delegation)
생성자 위임 규칙(Initializer Delegation)
(1) 델리게이트 업(Delegate up) - 지정 생성자는 항상 위(up)로 위임!
서브 클래스의 지정생성자는 슈퍼 클래스의 지정생성자(Designated Initializer)를 반드시 호출해야 한다.
(2) 델리게이트 어크로스(Delegate across) - 편의 생성자는 항상 옆(across)으로 위임!
편의 생성자는 동일클래스의 다른 생성자(그게 지정생성자(Designated)든 편의생성자(Convenience)든) 호출해야하며, 최종적으로 지정 생성자를 호출해야 한다.
아래는🍎공식문서의 Initializer Delegation을 모델로 표현한 것
위의 설명까지가 이해가 되었다면, 클래스의 상속과 초기화 과정을 2스텝으로 이해해 볼 수 있다.
클래스의 상속과 초기화 과정은 2단계로 분류된다.
⭐️클래스의 2단계 재정의
생성자는 기본적으로 상속이 되지 않는 재정의가 원칙이다.
(생성자는 항상 모든 속성을 그때그때 초기화하기 때문에, 서브클래스에 최적화X)
고려할 점: 상위 지정생성자 + 현재단계의 저장 속성
1단계(필수) -> 지금까지 질리도록 말했던 것
1. 현 단계 클래스의 모든 저장속성 값의 메모리 초기화
2. 상위 지정생성자로 위임(delegate up)하여 상위 클래스의 저장속성 값의 메모리 초기화
3. 최상위 체인(제일 높은 클래스)까지 도달해 모든 저장속성이 완전히 초기화되었다면 인스턴스 정상적으로 생성된다.
2단계(선택)
필수 사항은 X
상속된 최상위 클래스에서 아래로 내려가면서 호출된 각각의 생성자는 인스턴스를 추가적으로 커스텀(Customize) 가능
커스텀(Customize)? -> 새로운 값 셋팅, 메서드 호출도 가능
self로 속성에 접근하여 속성 값 수정, 인스턴스 메서드의 호출도 가능해짐
WHY 최상위 클래스에서 아래로 내려올까?
클래스의 메서드 호출은 STACK방식으로 이루어지므로 LIFO(Last In First Out).
2단계 초기화의 이유?
안전한 초기화를 위해서(다른 초기화에 의해 값이 덮어씌어지는 문제가 발생할 수 있기 떄문에 이를 방지하기 위해서)
https://babbab2.tistory.com/170
Swift) 초기화(Initializers) 이해하기 (4/6) - 클래스의 2단계 초기화 및 상속
안녕하세요 :) 소들입니당 오늘부터 저는 일찍 일어나는 새가 일찍 졸리다..를 시전할 예정 난 파워 P인데 분명.. 인생 계획만큼은 늘 J로 세우는.. 사람 이랄까..? 쨌든 이번 포스팅은 지겨운 Initia
babbab2.tistory.com
아래는 🍎공식문서의 2단계의 초기화에 관한 그림을 통해 모델링한 것
4. 생성자 자동 상속(Automatic Initializer Inheritance) - 예외사항
생성자 자동 상속 (Automatic Initializer Inheritance)
위에서 언급했듯, 절대적으로 서브클래스는 상위클래스의 초기화 구문을 상속하지 않는다. 하지만 아래의 특정 조건들을 충족하면 상위클래스의 생성자를 자동적으로 상속받을 수 있게 된다.
규칙 1
서브클래스에서 서브클래스의 저장속성이 초기화되었고, 별도의 지정생성자(Designated Initializer)를 구현X
-> 슈퍼클래스의 지정생성자 자동 상속
규칙2
규칙1 방식으로 (서브클래스에서 슈퍼클래스의 생성자를) 자동상속 받거나, 슈퍼클래스의 지정생성자를 모두 재정의한다면,
슈퍼클래스의 편의생성자(Convenience Initializer)는 자동 상속
class Person {
var name: String
init (name: String) {
self.name = name
}
convenience init() {
self.init (name: "Unknown") }
}
class Student: Person {
var major: String = "Swift"
var age: Int? //옵셔널(서브클래스에서 서브클래스의 저장속성이 초기화됨)
//별도의 지정생성자 구현 X
}
//규칙 1: 부모클래스의 지정생성자 자동상속
let james: Person = Person (name: "james")
let steve: Student = Student (name: "steve" )
print (james.name) //james
print (steve.name) //steve
//규칙 2: 부모클래스의 편의생성자 자동상속
let bond: Person = Person()
let jobs: Student = Student()
print (bond.name) // Unknown
print (jobs.name) // Unknown
결국 Initializer Delegation의 최종 목적은 단순하다.
생성자에서 또 다른 생성자를 호출하여 초기화 코드의 중복을 최소화하고 편하게 class를 사용하자는 것이다.
5. 마무리
이번단원은 생성자, 넓게보면 구조체와 클래스라는 대단원에서 가장 어렵고 헷갈릴 수 있는 단원이다.
하지만 최초에 정의했던 2가지 법칙만 기억하면된다.
현재(Present) 단계에서 선언한 속성 값 초기화(Initialization)
상위클래스의 속성 값은 super키워드를 통해 위임(Delegate)
편의생성자(Convenience Initializer)도 결국에는 동일 클래스의 지정생성자를 거치는 보조 생성자에 불과하다.
생성자 위임 규칙(Initializer Delegation)의
델리게이트 업(Delegate Up)
델리게이트 어크로스(Delegate Across)도
지정생성자, 편의생성자를 만들때 지켜야하는 당연한 규칙임을 이해하게 된다.
클래스의 2단계 재정의중 1단계는 필수사항으로 위에서 다 쓴것을 요약한 것이며
2단계는 선택사항으로, self키워드로의 접근과 커스텀(Customize)이 가능했다는것.
예외사항인 자동상속도 현재 단계의 저장속성값이 셋팅되고, 별도의 지정생성자를 구현하지 않았다면,
자동상속의 권한을 갖게되며 편의생성자도 자동상속할 수 있다는 것(규칙2)
결국에는 저장속성 값이 초기화 되었는지만 꼼꼼하게 체크한다면
생성자라는 건 (그게 지정이든,편의든, 다음에 등장할 필수든) 매우 쉬워지게 된다.
원래 예제들까지 한번에 묶어 포스팅하려 했으나, 너무 길어져 다음 포스팅은 지정, 편의 생성자에 관한 예제로만 포스팅하고 그 다음에 마지막으로 필수생성자와 실패가능생성자에 대해 소개하며 초기화와 생성자에 대한 정리를 마친다.
출처
Inflearn Allen님의 강의 노트
Swift 공식문서
야곰님의 스위프트 프로그래밍 3판
'Swift' 카테고리의 다른 글
Swift [초기화와 생성자 3부(끝) - Required/Failable/Deinitializers] (0) | 2023.02.23 |
---|---|
Swift [Designated/Convenience 예제 모음] (0) | 2023.02.22 |
Swift [초기화와 생성자 1부 - Initialization/Memberwise Initializer/ Failable Initializer] (0) | 2023.02.20 |
Swift [상속과 재정의(Inheritance/Overriding)] (0) | 2023.02.18 |
Swift [Methods, Subscripts, Access Control, Singleton] (0) | 2023.02.17 |