안녕하세요! 피피아노입니다🎵
이번 포스팅은 저번 포스팅들에 이어서 초기화에 대해 정리해볼 건데 이번에 다룰 내용은 요구 이니셜라이저입니다!
그럼 바로 시작하겠습니다!
요구 이니셜라이저란?
요구 이니셜라이저(required initailzer)는 '반드시 필요한 초기화 방법'을 의미합니다.
클래스를 만들 때, 그 클래스를 기반으로 만들어지는 모든 서브클래스가 반드시 따라야 하는 초기화 규칙을 정하는 것입니다. 이 규칙은 'required'라는 키워드를 사용해서 정합니다.
이게 무슨 말인지 잘 모르겠죠? 좀 더 쉽게 설명해보겠습니다.
예를 들어, 핸드폰을 생산하는 회사가 있다고 생각해보겠습니다. 이 회사는 모든 핸드폰이 반드시 '제품 번호'를 가지도록 만들어야 합니다. 그래야 각 핸드폰을 식별하거나 문제가 생겼을 때 서비스를 제공할 수 있죠.
이럴 때 '제품 번호'를 초기화하는 이니셜라이저를 '요구 이니셜라이저'로 지정하면, 이 회사의 모든 핸드폰 모델(서브클래스)은 '제품 번호'를 가지도록 만들어야 합니다.
만약 어떤 모델에서 '제품 번호'를 초기화하지 않으면, 그 모델은 만들어질 수 없습니다. 컴퓨터 언어에서 이런 상황을 '컴파일 에러'라고 부릅니다.
요구 이니셜라이저는 이런 식으로, 클래스를 만드는 사람이 '이 클래스를 상속받는 모든 클래스는 반드시 이런 규칙을 따라야 해!'라고 명시할 때 사용하는 도구입니다. 이를 통해 안전하게 클래스를 설계하고, 오류를 미리 방지할 수 있습니다.
이제 이해가 좀 되셨을까요??
조금 더 추가 설명을 해보자면,
제품 번호를 초기화한다는 것은 제품 번호를 지우는 것이 아니라, 처음에 제품 번호를 설정하는 것을 의미합니다.
초기화라는 용어는 프로그래밍에서 변수나 객체를 처음 상태로 만드는 과정을 말합니다. 이 때의 '처음 상태'는 변수나 객체가 처음 값을 가지는 상태를 말하며, 이 값은 프로그래머가 설정한 기본 값일 수도 있고, 사용자가 입력한 값일 수도 있습니다.
예를 들어, 핸드폰 제조 회사에서 새로운 핸드폰을 만들 때, 각 핸드폰에는 고유한 제품 번호를 부여합니다. 이 때, 이 제품 번호를 처음 설정하는 과정을 '제품 번호를 초기화한다'라고 표현합니다.
이를 코드로 표현하면, 핸드폰 클래스의 요구 이니셜라이저에서 제품 번호를 초기화하는 과정은 다음과 같습니다.
class CellPhone {
var productNumber: String
required init(productNumber: String) {
self.productNumber = productNumber
}
}
이 코드에서 init(productNumber: String) 부분이 요구 이니셜라이저입니다. 새로운 핸드폰 객체를 만들 때, 이 이니셜라이저를 통해 제품 번호를 처음 설정하게 됩니다. 이렇게 설정한 제품 번호는 핸드폰 객체의 '초기 상태'가 되며, 이후에 이 값을 변경하거나 사용할 수 있습니다.
요구 이니셜라이저 재구현
요구 이니셜라이저 재구현(reimplementation of a required initializer)은 서브클래스에서 부모 클래스의 요구 이니셜라이저를 다시 구현하는 것을 말합니다.
상속받은 클래스는 부모 클래스의 요구 이니셜라이저를 반드시 구현해야 합니다. 이 때, 서브클래스에서 요구 이니셜라이저를 그대로 상속받을 수도 있지만, 필요에 따라 재구현하여 다른 동작을 수행하도록 만들 수 있습니다. 이를 요구 이니셜라이저 재구현이라고 합니다.
예를 들어, 다음과 같이 부모 클래스에 요구 이니셜라이저가 정의되어 있다고 가정해봅시다.
class SomeClass {
required init() {
// ... 초기화 코드 ...
}
}
이 경우, 상속받은 클래스에서는 다음과 같이 요구 이니셜라이저를 재구현할 수 있습니다.
class SomeSubClass: SomeClass {
required init() {
// ... 다른 초기화 코드 ...
super.init()
}
}
이렇게 요구 이니셜라이저를 재구현하면, 서브클래스에서는 부모 클래스의 초기화 방식과 다른 방식으로 객체를 초기화할 수 있습니다. 이는 서브클래스가 부모 클래스의 동작을 확장하거나 변경할 수 있도록 해주는 중요한 메커니즘 중 하나입니다.
아까 휴대폰을 가지고 예시를 들었는데 이번에도 해당 예시를 기반으로 한 번 예제 소스를 만들어보겠습니다.
class CellPhone {
var productNumber: String
required init(productNumber: String) {
self.productNumber = productNumber
}
}
class SmartPhone: CellPhone {
var operatingSystem: String
init(productNumber: String, operatingSystem: String) {
self.operatingSystem = operatingSystem
super.init(productNumber: productNumber)
}
required convenience init(productNumber: String) {
self.init(productNumber: productNumber, operatingSystem: "Default OS")
}
}
위의 코드에서 CellPhone 클래스는 productNumber를 요구 이니셜라이저로 가지고 있습니다. 이 클래스를 상속받는 SmartPhone 클래스는 이 요구 이니셜라이저를 재구현해야 합니다.
SmartPhone에서는 자신만의 속성인 operatingSystem을 추가하고, 이를 초기화하는 새로운 이니셜라이저를 정의하였습니다. 그리고 요구 이니셜라이저는 이 새로운 이니셜라이저를 호출하는 '편의 이니셜라이저(convenience initializer)'로 재구현하였습니다. 이 편의 이니셜라이저는 'Default OS'라는 기본 운영체제를 가지는 스마트폰 객체를 생성합니다.
이렇게 요구 이니셜라이저를 재구현하면, 서브클래스는 부모 클래스의 초기화 방식을 유지하면서도 자신만의 초기화 방식을 추가할 수 있습니다.
일반 이니셜라이저의 요구 이니셜라이저 변경
만약 부모클래스의 일반 이니셜라이저를 자신의 클래스로부터 요구 이니셜라이저로 변경할 수 있습니다.
그럴 때는 required override를 명시해주어 재정의됨과 동시에 요구 이니셜라이저가 될 것임을 명시해주어야 합니다.
또, 편의 이니셜라이저로 변경될 수 있습니다. 이것도 마찬가지로 required convienience를 명시해줘서 편의 이니셜라이저가 앞으로 요구될 것임을 명시해주면 됩니다.
코드로 한번 살펴보겠습니다.
class CellPhone {
var productNumber: String
init(productNumber: String) {
self.productNumber = productNumber
}
}
class SmartPhone: CellPhone {
var operatingSystem: String
required override init(productNumber: String) {
self.operatingSystem = "Default OS"
super.init(productNumber: productNumber)
}
}
위의 코드에서 CellPhone 클래스는 productNumber를 초기화하는 일반 이니셜라이저를 가지고 있습니다. SmartPhone 클래스는 이 이니셜라이저를 required override init(productNumber: String)으로 재정의하여 요구 이니셜라이저로 변경하였습니다.
이렇게 하면 SmartPhone 클래스를 상속하는 모든 서브클래스는 init(productNumber: String) 이니셜라이저를 반드시 구현해야 합니다.
요구 이니셜라이저를 편의 이니셜라이저 변경
아래 코드는 요구 이니셜라이저를 편의 이니셜라이저로 변경하는 코드입니다.
class CellPhone {
var productNumber: String
init(productNumber: String) {
self.productNumber = productNumber
}
}
class SmartPhone: CellPhone {
var operatingSystem: String
required init(productNumber: String, operatingSystem: String) {
self.operatingSystem = operatingSystem
super.init(productNumber: productNumber)
}
required convenience init(productNumber: String) {
self.init(productNumber: productNumber, operatingSystem: "Default OS")
}
}
위의 코드에서 SmartPhone 클래스는 init(productNumber: String, operatingSystem: String)을 요구 이니셜라이저로 가지고 있습니다. 이 클래스는 이 요구 이니셜라이저를 기반으로 init(productNumber: String) 편의 이니셜라이저를 추가로 구현하였습니다.
이 편의 이니셜라이저는 'Default OS'라는 기본 운영체제를 가지는 스마트폰 객체를 생성합니다. 이 편의 이니셜라이저 역시 요구 이니셜라이저로 변경되어, SmartPhone 클래스를 상속하는 모든 서브클래스는 이 편의 이니셜라이저를 반드시 구현해야 합니다.
지금까지 요구 이니셜라이저에 대해 알아보았습니다. 요구 이니셜라이저는 클래스를 설계할 때 중요한 도구로, 특히 클래스의 상속 구조를 설계하고 관리할 때 유용하게 사용됩니다. 이를 통해 안정적인 코드를 작성하고 예상치 못한 오류를 방지할 수 있습니다.
다른 궁금한 점이 있으시다면 언제든지 질문해주세요!! :)
감사합니다.
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > Swift' 카테고리의 다른 글
[Swift] 배열(Array) (0) | 2024.03.13 |
---|---|
[Swift] 초기화(Initializers) 알아보기 (6) - Failable Initalizers (0) | 2024.02.22 |
[Swift] 초기화(Initializers) 알아보기 (4) - 이니셜라이저의 상속 재정의와 자동 상속 (10) | 2024.02.13 |
[Swift] 초기화(Initializers) 알아보기 (3) - 2단계 초기화 (0) | 2024.02.06 |
[Swift] 초기화(Initializers) 알아보기 (2) - 구조체, Memberwise, 클래스의 초기화, 지정/편의 초기화 (0) | 2024.02.04 |