Swift) 초기화(Initializers) 이해하기 (2/6) - 클래스의 초기화(Designated / Convenience)
안녕하세요 :) 코들입니다..
코로나에 걸려 죽다 살아 돌아온 소들이라 볼 수 있습니다
아직도 마지막 잎새마냥 기침하며 포스팅 적는 중..
...뭐야
위 세 줄 쓴 뒤로 3주지남.. 진짜 죽었었나..
쨌든
저번 포스팅에서 구조체의 초기화에 대해서 공부했다면,
이번 포스팅에선 클래스의 초기화에 대해 공부를 해보려고 합니당!!
이전 포스팅에서 초기화가 무엇인지, 어떤 식으로 진행하는지까지 모두 공부 했으니
그것을 안다는 가정 하에 공부해보겠습니당 🫠
모든 포스팅은 편의 말투로 합니다~!!
1. 클래스(Class)를 초기화 하는 방법
이전 포스팅에서 초기화의 개념에서 가장 중요한 것이 뭐라고 했음!??!
바로바로
초기화 시점에 모든 프로퍼티들은 기본 값을 가지고 있어야 한다
만약 기본 값이 없는 프로퍼티가 있을 경우, 초기화에 실패하여 인스턴스가 생성되지 않는다
라고 했잖음!?
위 말은 정말 중요하니 생각하며 가봅시당
클래스를 초기화 하는 방법은 구조체랑 똑~~~~~~~같으니
대충 복붙점 해봄
1-1. 선언과 동시에 프로퍼티에 기본값 넣어주기
class Human {
let name: String = "Sodeul"
let age: Int = 28
}
|
위처럼 name이란 프로퍼티에 Sodeul이란 값을, age란 값엔 28이란 값을
직접 선언과 동시에 기본값을 넣어주며 초기화를 해줘버리는 것임
이렇게 선언과 동시에 기본값으로 초기화를 시킬 수 있음!!!
2-2. 프로퍼티 타입을 옵셔널(Optional) 타입의 변수로 설정한다
class Human {
var name: String?
var age: Int?
}
|
위처럼 name, age 값을 옵셔널 타입의 변수로 설정하는 것 또한
초기화를 진행해준 것임
따라서 만약 값이 있을지도 없을지도 모를 경우에는 위처럼 옵셔널 타입으로 설정하면 됨
그러면 초기화를 할때 옵셔널 타입인 name과 age는 자동으로 nil로 초기화가 됨
근데 왜 "변수" 인가요?? "상수"는 안되나염?
class Human {
let name: String?
let age: Int? // error! Class 'Human' has no initializers
}
|
넹 안 돼염
위 코드처럼 let으로 선언하고 기본값을 안 주면 에러가 뜸
초기값이 없는 name, age를 initializers를 통해 초기화 해야 하는데 initializers가 없어!!! 라고 뜨는 것임
생각해보면 당연한 것 같음
let으로 선언할 경우 옵셔널 타입 프로퍼티 age는 생성자가 불릴 때 nil로 초기화 될텐데,
age란 값은 상수기 때문에 더이상 nil 외에 값을 가질 수 없어서 에러가 나는 것.. 아..닐까..?
(당연하다 해놓고 의문문으로 끝내기ㅎ;; 아니면 피드백 주세여)
따라서 var로 선언하면 문제 없이 nil로 초기화 됨!!!
1-3. init 함수에서 값을 설정해주기
class Human {
var name: String?
let nickName: String = "Sodeul"
let age: Int
init(name: String) {
self.age = 28
}
}
|
만약 하나의 프로퍼티라도 기본 값을 지니지 않거나, 옵셔널 타입의 변수가 아니라면
이땐 우린 초기화를 진행하는 함수인 init 함수(생성자)를 통해 나머지 놈들도 초기화를 진행 해줘야 함!
따라서 위처럼 생성자 안에서 해당 프로퍼티들을 초기화 시킬 수도 있음
(만약 모든 프로퍼티가 기본값을 지니거나 옵셔널 타입의 변수거나 해서 초기값을 갖는다면
그땐 생성자를 따로 선언해줄 필요가 없음!)
중요한 건 뭐라구!?!?
init(초기화) 함수가 종료되기 전까진 모든 프로퍼티가 기본 값을 가져야 한다!!!
뭐 위처럼 init 함수에서 파라미터를 직접 받아서 초기화를 진행할 수 있음
.
.
쨌든 기본 값을 지정하든, 옵셔널 타입의 변수로 설정하든, init 함수에서 작성하든 셋을 짬뽕해서 쓰든
결론은 모든 프로퍼티는 초기화 시점에 초기값을 갖고 있어야 한다
2. 구조체는 기본값 초기화도 안 하고, init 함수도 안 만들어도 에러 안 나던데 클래스는 에러 나네여?
struct HumanStruct {
let name: String
let age: Int
}
class HumanClass { // Class 'HumanClass' has no initializers
let name: String
let age: Int
}
|
분명 똑같이 프로퍼티 name, age를 초기화 하지 않은 구조체와 클래스를 만들었을 경우,
클래스만 initializers를 갖지 않는다면서 에러가 남
이 이유에 대해서 이전 포스팅에서 공부 했잖음?
구조체의 경우 초기화되지 않은 프로퍼티를 초기화 시킬 수 있도록
"자동으로" 제공하는 생성자가 있는데, 그것이 바로 Memberwise Initializers 라고 했었음
그러면 클래스는 Memberwise Initializers 제공 안하나요?
넹ㅜ 안 합니다.
따라서 프로퍼티의 초기값를 위에서 공부한 방식으로 지정시켜 주어야 함
3. 클래스의 Initializers
클래스는 initializers를 총 두 가지로 나누어서 볼 수 있음
여기서부터 살~짝 어려울 수 있으니 차근차근 이해해봅시당
3-1. 지정 생성자(Designated Initializers)
클래스의 모든 프로퍼티를 초기화 하는 생성자
엥 우리가 지금껏 init 키워드만 갖고 쓰던 게 모든 프로퍼티를 초기화 하려고 쓴 거 아닌가여?
맞음!! 우리가 그렇게 썼던 모든 생성자의 풀 네임은 지정 생성자(Designated Initializers임!
class Human {
let name: String
let age: Int
init(name: String) {
self.name = name
self.age = 28
}
}
|
이런식으로 썼던 위 init 함수를 우린 그냥 init 함수! 초기화 함수! 생성자! 하고 불렀지만
실제 이름은 바로 Designated Initializers임!
이 Designated Initializers의 핵심은 두 가지임
1️⃣ 해당 생성자 메서드가 종료되기 전까지, 생성자 안에 모든 프로퍼티는 "초기값"을 지니고 있어야 한다
한 100번 말한 듯ㅎㅎ;
옵셔널 변수로 선언 되지 않았거나, 기본 값을 갖지 않는 프로퍼티가 있을 경우엔
생성자를 추가하여 초기값을 지정 해줘야 된다고 했음!
2️⃣ 반드시 super 클래스의 생성자를 호출해야 한다
class Human {
let name: String
init(name: String) {
self.name = name
}
}
class Sodeul: Human {
let alias: String
init(alias: String) { // error! 'super.init' isn't called on all paths before returning from initializer
self.alias = alias
}
}
|
Designated Initializers의 핵심 중 또 한가지는,
상속 받은 서브 클래스에서 Designated Initializers를 작성할 경우,
반드시 슈퍼 클래스의 Initializers를 호출해주어야 함!!!
만약 위처럼 내 프로퍼티만 모두 초기화 시키는 init 함수를 작성할 경우,
내 부모(슈퍼) 클래스의 프로퍼티 중 만약 기본값이나 옵셔널 타입의 변수가 아니라서
초기값을 가지지 않았다면 초기화 안 되는데 어쩔겨!!!
class Human {
let name: String
init(name: String) {
self.name = name
}
}
class Sodeul: Human {
let alias: String
init(alias: String) {
self.alias = alias
super.init(name: alias)
}
}
|
따라서 이렇게 super 클래스의 생성자를 init에서 직접 호출해줘서
부모 클래스의 초기화도 진행해주어야만 에러가 사라짐!
3-2. 편의 생성자(Convenience Initializers)
모든 프로퍼티를 초기화 할 필요 없는 생성자로, 반드시 다른 초기화를 호출시켜야 한다
이때 슈퍼 클래스의 생성자는 호출 시킬 순 없고,
같은 클래스 내에 있는 Convenience Initializers나 Designated Initializers를 호출 시켜야 한다
최종적으론 같은 클래스 내의 Designated Initializers가 호출되어야 한다
정의 참 어렵다 ㅎㅎ;;
자 좀 더 쉽게 얘기해봅시당
Convenience Initializers란 단어 뜻대로 이해를 해보자면 편의 초기화라는 거잖음?
따라서 이 Convenience Initializers는,
위에서 말했던 모든 프로퍼티를 초기화 해주는 Designated Initializers를 "도와주는 역할"임!!!!!
어떻게!?! Designated Initializers의 파라미터 중 원하는 값을 기본값으로 설정해서!!!
class Sodeul {
var name: String
var nickName: String
init(name: String, nickName: String) {
self.name = name
self.nickName = nickName
}
convenience init(name: String) {
self.init(name: name, nickName: "unknown")
}
}
|
이런 식으루!!!
convenience Initializers의 가장 큰 핵심은
반드시 같은 클래스(슈퍼 클래스X)에 있는 "다른 초기화" 놈을 또 호출 시켜야 한단 것임
그것이 convenience든 Designated든!!
근데 이제 뭐다?
Convenience Initializers는 Designated Initializers를 "보조"하는 역할이기 때문에
최종적으로는, 같은 클래스 내에 있는 Designated Initializers를 호출 시켜야 함
위 그림과 같이!!
여기까지 정리하자면,
Convenience Initializers는 "보조" 생성자이기 때문에, 굳이 모든 프로퍼티를 초기화 할 필요가 없어
그럼 초기화 안 된 프로퍼티가 있으면 어케?
그건 최종적으로 같은 클래스 내에 있는 Designated Initializers를 호출 시키기 때문에 문제 없어!
왜냐? Designated Initializers는 모든 프로퍼티를 초기화 시킬테니까!!
Convenience init은 지네끼리 호출하고 난리쳐도
결국 마지막엔 Designated로 귀결 된다!! 하하!
따라서 위와 같은 코드를 작성할 수 있는 것!!
convenience init이 또 다른 convenience init을 호출하고,
그 convenience init에선 결국 최종적으로 designated init을 호출함!!
따라서, Convenience init을 사용하려면,
그 전에 먼저 Designated initializers가 필히 구현 되어 있어야 함!
그래야 최종적으로 부를 수 있으니께..
만약, Convenience initializers에서 최종적으로 Designated Initializers를 호출하지 않으면,
에러낭요
+ Convenience Initializers에서는 프로퍼티 값 설정 못하나여?ㅠ
설정하려고 하면 'self' used before self.init call or assignment to 'self' 에러 뜨는데염 ㅠ
위 처럼 에러가 뜰 때는 다른 Initializer를 먼저 불른 후에 접근해주면 됨!!
이런 식으루!! :)
이 이유는 다른 초기화를 부르기 전에 값을 설정하면,
다른 초기화에 의해 값이 덮어씌어지는 문제가 발생할 수 있기 떄문인데
이는 다다음 포스팅 클래스 생성자의 2단계 초기화를 공부할 때 다시 보겠음!!
3. 어려우니께 간단하게 정리
개념이 어려웠을 수 있응께
가장 중요한 핵심 몇 가지만 정리 하고 가겠음
생성자의 역할은, 모든 프로퍼티의 값들을 초기화 하는 것이다!
따라서 하나라도 초기화 되지 않은 프로퍼티가 있으면 안된당! 인스턴스 생성 안 됨!
이때, 클래스의 생성자는 총 두 가지의 종류가 있다!
Designated Initializers
우리가 아무렇지 않게 init 하고 썼던 초기화로
init 함수가 끝나기 전엔 모든 프로퍼티의 값이 초기화 되어 있어야 한다!
(모든 프로퍼티가 기본 값을 갖거나 옵셔널 타입의 변수로 선언된 경우엔 작성하지 않아도 됨!)
Convenience Initializers
Designated Initializers의 초기화를 "보조"해주는 역할의 초기화다!
Designated Initializers의 파라미터 중 일부를 기본값으로 설정해서 호출할 수 있다!
이 Convenience Initializers는 반드시 다른 초기화 메서드를 호출시켜주어야 하는데,
이때 다른 초기화 메서드란,
Convenience / Designated든 상관 없이 같은 클래스 내에 있는 초기화 메서드기만 하면 된다
다만!!!!!! 최종적으론 반드시 같은 클래스(계층) 내에 있는 Designated Initializers가 호출되어야 한다!
.
.
.
으 꽤 기네
길고 길었던 처음 접하면 생각보다 꽤 이해하기 힘든
클래스의 생성자 포스팅 끝!!
근데 아직 클래스의 생성자는 상속이 언제 되냐에 대한 더 어려운 다음 글이 남아있답니다^^
잘못된 내용 및 오타/피드백은 댓글로 달아주세요~~!!