[Swift] Initialization (1) | 생성자의 종류 : 지정생성자, 편의생성자, 필수생성자, 실패가능생성자
기본 생성자 외에 지정 생성자, 편의 생성자, 실패 가능 생성자, 필수 생성자, 멤버와이즈 생성자에 대해 알고 계신가요? 아니면 들어본 적이 있으신가요?? 저는 이번에 공부하면서 제대로 알게 되어서 한번 정리해 보려고요!
한 번도 편의 생성자나 실패 가능 생성자를 사용해서 코드를 짜본 적은 없지만 공부를 하면서 이런 다양한 생성자를 잘 활용하면 중복 코드 없이 가독성 있고 활용도 높은 타입을 만들 수 있겠다고 생각했어요.
[Swift] Initialization (1) | 생성자의 종류 : 지정생성자, 편의생성자, 필수생성자, 실패가능생성자
[Swift] Initialization (2) | 상속, 확장에서의 생성자
# 🥨 생성자의 의미
일단, 우리가 클래스나 구조체를 만들 때 습관적(?)으로 구현했던 생성자 init 은 근복적으로는 어떤 의미를 가지고 있을까?
생성자는 “인스턴스를 만들기 위해 모든 속성을 초기화하는 도구”라고 볼 수 있습니다.
쉽게 설명하자면, 우리는 클래스나 구조체를 통해 인스턴스를 만들어 메모리에 그 데이터를 올려서 사용하고자하죠. 근데 인스턴스가 되려면 타입 내부의 저장속성들의 값이 있어야하는데 생성자에서는 그 값을 할당하는, 즉, 모든 저장속성을 초기화하는 역할을 하는 것입니다.
# 🥨 생성자의 종류
생성자의 종류에는 지정/편의/필수/실패가능 생성자가 있습니다.
- 지정 생성자와 편의 생성자
우선 지정생성자는 모든 저장속성을 파라미터로 갖는 생성자여서 가장 익숙할 거예요!
근데 편의생성자는 이름에서 들어가는 ”편의”라는 말처럼 일부 저장속성만을 파라미터로 갖는 생성자입니다. 좀 더 편리하게 인스턴스를 생성하기 위한 생성자죠! 파라미터를 일부만 받는다고 하지만 인스턴스 생성 시에는 모든 저장속성이 초기화되어 있어야겠죠??
그리고 뒤에서 나오겠지만 편의 생성자는 반드시 본인 단계에서의 편의생성자나 지정생성자를 호출해야한다는 원칙(델리게이트 어크로스)이 있고 궁극적으로는 본인단계의 지정생성자를 호출해야한다는 것을 기억해놓자!! ( 지정생성자만이 해당 단계의 모든 저장값을 초기화하기 때문에)
class SomeClass {
var a : String
var b : String
let c : String
//지정 생성자
init(a:String, b:String, c:String){
self.a = a
self.b = b
self.c = c
}
//편의 생성자
convenience init(a:String){
self.init(a: a, b: "b", c: "c") //델리게이트 어크로스 & 궁극적으로는 본인 단계의 지정생성자를 호출해야한다
}
}
let someClass = SomeClass(a: "a")
- 필수 생성자
필수 생성자는 상속할 때 하위 클래스에서 반드시 구현되어야 하는 생성자를 말합니다.
생성자를 만들 때 앞에 required 키워드를 붙여주면 되고 하위 클래스에서도 required를 사용하여 필수생성자를 구현해 주면 됩니다.
다만, 하위 클래스에서도 다른 생성자 구현하지 않았을 경우에는 자동상속되기 때문에 그 이외의 경우에만 구현해주면됩니다!
class SomeClass {
var a : String
var b : String
let c : String
//필수 생성자
required init(a:String, b:String, c:String){
self.a = a
self.b = b
self.c = c
}
}
class SubClass : SomeClass {
var d : String
init(d:String){
self.d = d
super.init(a: "a", b: "b", c: "c")
}
required init(a: String, b: String, c: String) {
self.d = "d"
super.init(a: a, b: b, c: c)
}
}
- 실패 가능 생성자
실패 가능 생성자(init?)라는 것은 인스턴스 생성에 실패할 수도 있는 생성자를 말하는데요. 인스턴스 생성에 실패하게 되면 nil 값을 반환하도록 만들 수 있습니다.
주로 사용해왔던 실패 불가능 생성자(init)처럼 실패가 불가능하게 만들어서 아얘 런타임 에러가 나서 앱이 완전히 꺼지는 것보다는 실패 가능 생성자(init?)로 정의해서 가능성을 관리해주는게 훨씬 나을 수 있는 선택일것 같아요!
그리고 여기서 특별한 점은 실패 가능 생성자로 만들어진 인스턴스는 반드시 옵셔널 타입으로 정의가 되어야 하는 것이죠! 아니면 아래 에러가 날 수 있습니다..!
Value of optional type 'SomeClass?' must be unwrapped to a value of type 'SomeClass’
class SomeClass {
var a : String
var b : String
let c : String
//실패가능 생성자
init?(a:String, b:String, c:String){
if a.isEmpty {
return nil
}
self.a = a
self.b = b
self.c = c
}
}
let someClass : SomeClass? = SomeClass(a: "a", b: "b", c: "c")
# 🥨 struct의 멤버와이즈 생성자
인스턴스를 만들기 위해서는 생성자가 반드시 필요하다고 했는데.. 근데 우리가 프로젝트를 하면서 구조체를 생성할 때에는 클래스와 다르게 저장속성들에 기본값이 없음에도 불구하고 생성자를 만들지 않아도 에러가 나지 않는 경험을 해보셨을 텐데요. 그럼 구조체는 생성자가 필요하지 않은 걸까요?
아닙니다. 다만, 구조체는 이런 경우 멤버와이즈 이니셜라이저를 자동생성해 주는데요!!
멤버와이즈 생성자란, 구조체에만 있는 특별한 생성자이며 지정생성자를 구현해주지 않았을 때 모든 저장속성을 파라미터로 가진 생성자입니다.
아래의 사진에서 볼 수 있듯이 SomeStruct의 저장속성에 대한 기본값을 지정해주지 않고 지정생성자도 만들지 않았음에도 불구하고 인스턴스를 생성할 때 모든 저장속성이 파라미터로 있는 생성자가 자동으로 만들어져 있죠?!
정말 신기.. 이러니 특별한 경우 아니면 구조체를 쓰는게 훨씬 편하고 신경쓸게 적어질 수 밖에 없죠 ㅎㅎ
필요한 경우 우리는 상속 혹은 확장을 종종 하곤 하는데 그때 상황별로 생성자의 재정의 등을 고려해주어야 할 필요가 있죠. 이 부분은 다음 글에서 다뤄보고자 합니다!
위에서 다룬 생성자의 종류에 대해 요약하자면 이렇습니다.
지정생성자
- 모든 저장속성을 파라미터로 갖는 생성자
편의 생성자
- 일부 저장속성만을 파라미터로 갖는 생성자
- class에서만 존재
- convenience 키워드 필요
- 반드시 본인 단계에서의 편의생성자나 지정생성자를 호출해야하며, 궁극적으로는 본인단계의 지정생성자를 호출해야 한다 (델리게이트 어크로스)
필수 생성자
- 상속할 때 하위 클래스에서 반드시 구현되어야 하는 생성자
- class에서만 존재
- required 키워드 필요
실패 가능 생성자
- 인스턴스 생성에 실패하게 되면 nil 값을 반환
- 실패 가능 생성자로 만들어진 인스턴스는 반드시 옵셔널 타입으로 정의해야 함
멤버와이즈 생성자
- 지정생성자를 구현해주지 않았을 때 자동제공되는 모든 저장속성을 파라미터로 가진 생성자
- struct에서만 존재