클래스의 이니셜라이저
값 타입(Struct
, Enum
)의 이니셜라이저에서는 위임을 위해 이니셜라이저끼리 구분할 필요가 없었지만, Class에서는 지정 이니셜라이저와 편의 이니셜라이저로 역할을 구분한다.
즉, 클래스가 부모클래스에서 상속한 모든 속성을 포함하여 모든 클래스의 저장 속성은 초기화하는 동안 초기 값을 지정해야 줘야 한다.
Swift에서는 모든 저장 프로퍼티가 기본값을 가지는데 편리하도록 클래스의 유형에 대해 두 가지 이니셜라이저를 정의한다.
지정 이니셜라이저
- 클래스의 주요 이니셜라이저
- 클래스의 모든 저장 프로퍼티를 초기화한다
- 부모클래스의 이니셜라이저를 호출할 수 있다
- 클래스 내부에는 반드시 한 개 이상의 지정 이니셜라이저가 존재해야 한다
- 값 타입 이니셜라이저를 정의할 때와 같은 형식인
init
키워드를 사용한다
init(매개 변수들) {
//초기화 구문
}
편의 이니셜라이저
- 초기화를 손쉽게 도와주는 역할을 함
- 지정 이니셜라이저를 자신(편의 이니셜라이저) 내부에서 호출함
- 지정 이니셜라이저의 일부 매개 변수의 초깃값 설정하여 초기화함
- 전달 인자로 초깃값을 전달할 필요 없이 항상 같은 값으로 초기화 가능함. 즉, 적은 입력으로 초기화를 편리하게 해 줌
- 만약 클래스에 편의 이니셜라이저를 쓸 필요가 없다면 사용하지 않아도 된다
init
키워드 앞에convenience
지정자를 명시하면 됨
convenience init(매개 변수들) {
// 초기화 구문
}
편의 이니셜라이저 단독으로 모든 프로퍼티를 초기화할 수 없다. 일부 프로퍼티만 처리한 뒤 다른 이니셜라이저를 통해 전체 초기화를 수행하고, 일반적으로 중복되는 초기화 코드 방지를 위해 사용한다
사용 방법
class Person {
var name: String
var age: Int
init(name: String, age: Int){
self.name = name
self.age = age
}
convenience init(name: String){
self.init(name: name, age: 27) // 지정 이니셜라이저 호출
}
}
let seogun: Person = Person(name: "서근")
print("이름: \(seogun.name). 나이: \(seogun.age)") //이름: 서근. 나이: 27
Person
클래스는 name
과 age
저장 프로퍼티에 대한 초기화가 필요한데, name
값만 입력받고 다른 메서드의 도움을 받아 처리하는 방식을 편의 이니셜라이저이다.
편의 이니셜라이저를 사용하지 않으려면 저장 프로퍼티에 초깃값을 할당해주면 된다.
class Person {
var name: String
var age: Int = 27
init(name: String){
self.name = name
}
}
let seogun: Person = Person(name: "서근")
print("이름: \(seogun.name). 나이: \(seogun.age)") //이름: 서근. 나이: 27
부모/자식 클래스 관계에서 아래처럼 만약 자식 클래스를 초기화할 때 부모클래스의 super.init()
이 먼저 호출되면 isToggle
메서드에서 bright
변수는 초기화되지 않은 상태로 호출되니 주의해야 한다.
class Toggle {
var on: Bool
var off: Bool
init(on: Bool, off: Bool) {
self.on = on
self.off = off
isToggle()
}
func isToggle() {
print("스위치 상태 on: \(self.on). off: \(self.off)")
}
}
class Bright: Toggle {
var bright: Int
init(bright: Int) {
self.bright = bright
//super init()이 bright 보다 먼저 호출 되면 bright는 초기화 되지 않음
super.init(on: true, off: false)
}
override func isToggle() {
super.isToggle()
print("bright: \(self.bright)")
}
}
let lamp: Bright = Bright(bright: 100)
이니셜라이저 위임 규칙
- 자식클래스의 지정 이니셜라이저는 부모클래스의 지정 이니셜라이저를 반드시 호출해야 함
- 편의 이니셜라이저는 반드시 자신을 정의한 클래스의 다른 이니셜라이저를 호출해야 함
- 편의 이니셜라이저는 지정 이니셜라이저를 호출해야 함
부모클래스는 하나의 지정 이니셜라이저(A)
와 두 개의 편의 이니셜라이저(1, 2)
가 존재한다. 부모클래스의 편의 이니셜라이저(2)
는 다른 편의 이니셜라이저(1)
을 호출하고 그 편의 이니셜라이저(1)
는 궁극적으로 지정 이니셜라이저(A)
를 호출한다. - 조건 2, 3 충족
부모클래스는 자신보다 조상인 부모를 갖지 않기 때문에 조건 1 은 해당사항이 없다.
자식클래스에는 두 개의 지정 이니셜라이저(B, C)
와 편의 이니셜라이저(3)
가 존재한다. 편의 이니셜라이저(3)
은 지정 이니셜라이저(C)
를 호출하고, 편의 이니셜라이저
는 자신의 클래스에 구현된 아니셜라이저만 호출 가능하므로 부모클래스의 이니셜라이저는 호출 불가하다. - 조건 2, 3 충족
또, 두 지정 이니셜라이저(B, C)
모두 부모클래스의 지정 이니셜라이저(A)
를 호출한다. - 조건 1 충족
이제 복잡한 모식도를 보며 지정 이니셜라이저
가 어떤 식으로 클래스의 이니셜라이저 중 기둥의 역할을 하는지 알아보면 이해하기 쉬울 겁니다.
2단계 초기화
- 자식 및 부모클래스의 모든 저장 프로퍼티에 초깃값을 지정한다
- 자식 및 부모클래스의 저장 프로퍼티 값을 변경한다. 그리고 메서드 및 연산 프로퍼티를 사용하여 초기화를 진행한다
class Aclass {
var name: String
var age: Int = 26
init(name: String){
self.name = name
}
}
class Bclass: Aclass {
var height: Int
init(height: Int, name: String) {
//자식 클래스의 속성 값에 값 할당
self.height = height
//자식클래스의 지정 이니셜라이저는
//부모클래스의 지정 이니셜라이저를 반드시 호출 해야함
super.init(name: name)
//부모클래스가 정의한 속성 값 변경
age = 27
}
}
let seogun: Bclass = Bclass(height: 200, name: "서근")
seogun.height //200
- 부모클래스를 호출하기 전, 자식 클래스의 프로퍼티를 초기화해야 한다
- 부모클래스에 있는 프로퍼티에 따로 값을 할당하기 위해서는 먼저 부모클래스를 호출해야 한다
- 하지만, 자식 클래스의 프로퍼티가 초기화된 경우에는 자식 클래스의 프로퍼티에 값을 할당하는 구분은 뒤에 동작해도 된다
이니셜라이저 상속
- Swift는 기본적으로 자식 클래스에서 부모클래스의 이니셜라이저를 상속하지 않는다. (무분별하게 상속되어 자식 클래스를 잘못 초기화하는 상황을 막기 위함)
- 하지만, 특정 조건을 만족하면 이니셜라이저를 상속함
이니셜라이저 상속을 위한 조건
- 자식클래스가 지정 이니셜라이저를 정의하지 않은 경우, 지정 이니셜라이저를 상속 받음
- 자식클래스가 부모클래스의 지정 이니셜라이저를 모두 구현(상속 또는 재정의)한 경우 편의 이니셜라이저를 상속 받음
class Food {
var name: String
init(name: String) { // Food 클래스의 지정 이니셜라이저
self.name = name // Food 클래스의 모든 프로퍼티를 초기화
}
convenience init() { // Food 클래스의 편의 이니셜라이저
// 지정 이니셜라이저를 호출하여 매개 변수에 "[이름 없음]"이란 값 전달
self.init(name: "[이름 없음]")
}
}
let namedMeat = Food(name: "베이컨") // namedMeat의 name은 “베이컨"
let mysteryMeat = Food() // mysteryMeat의 name은 “[이름 없음]"
class RecipeIngredient: Food { // Food를 상속받은 클래스
var quantity: Int
// RecipeIngredient 클래스의 지정 이니셜라이저
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name) // 부모클래스의 이니셜라이저를 호출하여 name에 값 전달
}
// 부모클래스의 이니셜라이저와 겹치기 때문에 override 키워드를
// 써서 오버라이드 (식별자는 매개변수의 타입과 이름으로 구분하기 때문)
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
// RecipeIngredient를 상속 받은 클래스
// 새로 추가된 모든 프로퍼티가 기본 값을 가지고 있으며 별도의
// 이니셜라이저를 적용하지 않았으므로 부모클래스의 모든 이니셜라이저를 자동으로 상속한다.
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "베이컨"),
ShoppingListItem(name: "달걀", quantity: 6),
]
// 참고: "[이름 없음]"을 "오렌지 쥬스"로 변경 및 구매한 것으로 변경
breakfastList[0].name = "오렌지 쥬스"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x 오렌지 쥬스 ✔
// 1 x 베이컨 ✘
// 6 x 달걀 ✘
전체 코드
<hide/>
class Food {
var name: String
init(name: String) { // Food 클래스의 지정 이니셜라이저
self.name = name // Food 클래스의 모든 프로퍼티를 초기화
}
convenience init() { // Food 클래스의 편의 이니셜라이저
// 지정 이니셜라이저를 호출하여 매개 변수에 "[이름 없음]"이란 값 전달
self.init(name: "[이름 없음]")
}
}
let namedMeat = Food(name: "베이컨") // namedMeat의 name은 “베이컨"
let mysteryMeat = Food() // mysteryMeat의 name은 “[이름 없음]"
class RecipeIngredient: Food { // Food를 상속받은 클래스
var quantity: Int
// RecipeIngredient 클래스의 지정 이니셜라이저
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name) // 부모클래스의 이니셜라이저를 호출하여 name에 값 전달
}
// 부모클래스의 이니셜라이저와 겹치기 때문에 override 키워드를
// 써서 오버라이드 (식별자는 매개변수의 타입과 이름으로 구분하기 때문)
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
// 부모클래스의 모든 이니셜라이저를 구현한 경우, 부모클래스가 가진 컨비니언스 이니셜라이저를 상속 받는다.
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "베이컨")
let sixEggs = RecipeIngredient(name: "달걀", quantity: 6)
// RecipeIngredient를 상속 받은 클래스
// 새로 추가된 모든 프로퍼티가 기본 값을 가지고 있으며 별도의
// 이니셜라이저를 적용하지 않았으므로 부모클래스의 모든 이니셜라이저를 자동으로 상속한다.
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "베이컨"),
ShoppingListItem(name: "달걀", quantity: 6),
]
// 참고: "[이름 없음]"을 "오렌지 쥬스"로 변경 및 구매한 것으로 변경
breakfastList[0].name = "오렌지 쥬스"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x 오렌지 쥬스 ✔
// 1 x 베이컨 ✘
// 6 x 달걀 ✘
코드 출처 : 미디엄 장국진
요구 이니셜라이저
- 모든 자식 클래스에서 반드시 구현해야 하는 이니셜라이저
- 클래스의 이니셜라이저 앞에
required 수식어
를 사용한다. 자식클래스에서 구현할 때도required
수식어 필수 required
는 기본적으로override
를 포함한다. (자식클래스에서 요구 이니셜라이저를 재정이 할 때는override
대신required
수식어 사용)
class Aclass {
var name: String
required init() {
// 이니셜라이저 구현부
self.name = "서근"
}
}
//Aclass를 상속받은 SubClass에 요구 이니셜라이저를 구현하지 않았지만,
//SubClass 클래스의 score 프로퍼티에 기본값이 있고 별다른 지정 이니셜라이저가 없기때문에
//이니셜라이저가 자동으로 상속 된것이다.
class SubClass: Aclass {
var score: Int = 0
}
let seogun: SubClass = SubClass()
Aclass
를 상속받은 SubClass
에 요구 이니셜라이저를 구현하지 않았지만, SubClass
클래스의 score
프로퍼티에 기본값이 있고 별다른 지정 이니셜라이저가 없기때문에 이니셜라이저가 자동으로 상속된 것이다.
만약에 SubClass
클래스에 새로운 지정 이니셜라이저를 구현하면 부모클래스로부터 이니셜라이저가 자동으로 상속되지 않기때문에 요구 이니셜라이저를 반드시 구현해줘야 한다.
class Aclass {
var name: String
//요구 이니셜라이저 정의
required init() {
self.name = "서근"
}
}
class SubClass: Aclass {
var score: Int = 0
init(score: Int) {
self.score = score
super.init()
}
required int() {
self.score = 50
super.init()
}
}
class SecondSubClass {
var grade: String
init(grade: String) {
self.grade = grade
super.init()
}
required init() {
self.grade = "A"
super.init()
}
}
let seogun: SubClass = SubClass()
print(seogun.score) // 50
let mijin: SubClass = SubClass(score: 90)
print(mijin.score) // 90
let cheolsu: SecondSubClass = SecondSubClass(grade: "B+")
print(cheolsu.grade) // B+
클로저를 활용한 이니셜라이저
- 기본 값 설정이 단순히 값을 할당하는 것이 아닌 다소 복잡한 계산을 필요로 한다면 클로저나 함수를 이용해 값을 초기화하는데 이용할 수 있다.
- 클로저가 끝난 뒤에 괄호
( )
를 붙여 속성이 클로저 자체가 아닌 연산의 결과를 가지도록 할 수 있다. - 연산 프로퍼티와 다른 점은 연산 프로퍼티는 변수를 참조할 때마다 호출되지만, 클로저를 활용한 이니셜라이저는 한 번만 호출되고, 상수가 아닌 변수로 선언할 경우 값을 변경할 수도 있다.
class SomeClass {
let someProperty: SomeType = {
// 이 클로저의 코드로 someProperty를 위한 기본 값을 생성한다.
// someValue는 SomeType과 반드시 같은 유형이어야 한다.
return someValue
}()
}
읽어주셔서 감사합니다 🤟
'SWIFT > Grammar' 카테고리의 다른 글
Swift : 기초문법 [프로토콜#1 - 프로퍼티 요구사항] (1) | 2022.01.25 |
---|---|
Swift : 기초문법 [타입 캐스팅(Type Casting)] (1) | 2022.01.24 |
Swift : 기초문법 [상속#2 재정의 override] (0) | 2022.01.23 |
Swift : 기초문법 [상속#1 - 클래스 상속(자식, 부모, 기반 클래스)] (0) | 2022.01.22 |
Swift : 기초문법 [서브스크립트(subscript)] (0) | 2022.01.21 |