일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- toy project
- 영화일기
- 토이프로젝트
- 코딩공부
- java
- SWIFT
- sopt ios
- 영화후기
- 자바공부
- 인프런
- 키노
- 영화
- 플러터
- 프로그래머스
- 티스토리챌린지
- 스프링 입문
- 영화기록
- SOPT
- 백준
- 자바 스터디
- 일기
- 영화리뷰
- inflearn
- 새벽녘 소소한 기록
- Flutter
- Flutter Toy Project
- 리뷰
- sopt 35기
- 자바
- 오블완
- Today
- Total
새벽의 기록
[SOPT] 문법 스터디 #6 초기화 본문
초기화
- 편의 초기화 (Convenience Initializer )에 대해 알아봅시다
- 초기화 위임 (Initialization Delegation)에 대해 알아봅시다.
- 실패 가능한 초기화 (Failable Initializer): init?를 사용하여 초기화 실패를 처리하는 방법
- 필수 초기화; required키워드에 대해 알아봅시다
초기화
1. 편의 초기화 (Convenience Initializer )에 대해 알아봅시다
Swift에서 클래스의 초기화는 안전성을 위해 두 단계 과정을 거친다.
첫 번째 단계에서 저장된 각 프로퍼티는 해당 클래스에 의해 초기값이 할당된다.
→ 지정 초기화(Designated Initializer) → 이게 맨날 쓰던 일반적인 초기화
첫 번째 단계로 인해 모든 프로퍼티의 초기값이 할당되면, 두 번째 단계로 해당 인스턴스를 사용할 수 있는 상태로 간주하기 전에 프로퍼티 값을 사용자 정의할 수 있는 기회가 제공된다.
→ 편의 초기화(Convenience Initializer)
→ 필수 x
→ 진짜 그냥 말 그대로 1단계 지정 초기화를 도와주는 역할
class Person {
var name: String
var age: Int
// 지정 초기화 메서드 (Designated Initializer)
init(name: String, age: Int) {
self.name = name
self.age = age
}
// 편의 초기화 메서드 (Convenience Initializer)
convenience init(name: String) {
self.init(name: name, age: 0) // age 기본값 설정
}
}
let person1 = Person(name: "Alice", age: 25)
let person2 = Person(name: "Bob") // age는 0으로 초기화
이런 식으로 Designated Initializers의 파라미터 중 일부를 기본값으로 설정해서 호출할 수 있음
중요한 건, 반드시 최종적으로는 self.init을 통해 동일 클래스 내 다른 초기화 메서드를 호출해야 한다.
또한 반드시 지정 초기화 메서드가 먼저(위에) 작성돼야 한다.
2. 초기화 위임 (Initialization Delegation)에 대해 알아봅시다.
초기화 위임(Initialization Delegation)은 생성자에서 또 다른 생성자를 호출하여 초기화 코드의 중복을 최대한 제거하고, 모든 프로퍼티를 효율적으로 초기화 하기위해 사용한다.
값 타입(구조체)과 참조(클래스)타입에서의 형식이 다르다.
→ 상속의 여부가 갈리기 때문
값 타입에서의 초기화 위임
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = yPos
}
init(pos: Int) {
x = pos
y = pos
}
}
위 코드에서 두 개의 initializer 모두 모든 프로퍼티 x, y를 초기화 해주고 있다.
여기서는 만약 y는 무조건 0으로 초기화 하려고 코드를 바꾸려고 한다면,
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = 0
}
init(pos: Int) {
x = pos
y = 0
}
}
이런 식으로 모든 init을 수정해서 y값을 바꿔줘야한다.
→ 이는 중복된 코드로 **초기화 위임(Initialization Delegation)**을 따르고 있지 않는 것
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = yPos
}
init(pos: Int) {
self.init(xPos: pos, yPos: pos)
}
}
이렇게 모든 프로퍼티를 초기화하는 initializer를 하나 만들고, 다른 initializer가 위의 initializer 를 사용하게 만드는 것이 초기화 위임(Initialization Delegation)을 따르는 것!
이럴 경우, 초기화 코드의 중복도 제거할 수 있고 유지보수도 더 쉬움
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = 0
}
init(pos: Int) {
self.init(xPos: pos, yPos: pos)
}
}
위처럼 모든 initializer를 수정하지 않고, 모든 프로퍼티를 초기화하는 initializer 하나만 수정해도 됨
참조 타입에서의 초기화 위임
사전 지식
클래스의 생성자는 총 두 가지
지정 초기화(Designated Initializers)
우리가 평범하게 init 하고 사용했던 초기화로, init 함수가 끝나기 전엔 모든 프로퍼티의 값이 초기화 되어 있어야 한다.
(모든 프로퍼티가 기본 값을 갖거나 옵셔널 타입의 변수로 선언된 경우엔 작성하지 않아도 됨)
서브 클래스인 경우, super 클래스의 init 메서드를 반드시 호출해줘야 한다.
편의 초기화(Convenience Initializers)
지정초기화를 보조해주는 역할의 초기화. Designated Initializers의 파라미터 중 일부를 기본값으로 설정해서 호출할 수 있다.
편의 초기화는 반드시 다른 초기화 메서드를 호출시켜주어야 하는데,
이 때 Convenience / Designated든 상관 없이 같은 클래스 내에 있는 초기화 메서드기만 하면 된다.
다만! 최종적으로는 반드시 같은 클래스(계층) 내에 있는 지정초기화(Designated Initializers)가 호출되어야 한다.
클래스의 초기화 위임은 3가지 규칙을 따라야한다.
1️⃣ Designated Initializer는 반드시 슈퍼 클래스의 Designated Initializer를 호출해야 한다.
(슈퍼 클래스의 Designated Initializer를 호출하는 다른 이니셜라이저를 호출하는 것도 안 됨)
2️⃣ Convenience Initializer는 반드시 동일한 계층(클래스)의 initailizer를 호출해야 한다.
3️⃣ Convenience Initializer는 최종적으로 동일한 계층(클래스)의 Designated initialize를 호출해야 한다.
3. 실패 가능한 초기화 (Failable Initializer): init?를 사용하여 초기화 실패를 처리하는 방법
기존 생성자(Nonfailable Initializers)는 컴파일 시점에 모든 프로퍼티가 초기화 되어야 하기 때문에 초기화에 실패할 경우, 컴파일 에러가 발생한다.
하지만 Failable Initializers는 초기화에 실패하더라도 에러가 발생하지 않고 nil을 리턴한다.
→ 생성자의 Optional 버전?
실패 가능성을 표현하기 위해 init? 키워드를 사용하며, 초기화 실패 시 nil을 반환
class Product {
var name: String
var price: Double
// 가격이 0 이상일 때만 초기화 성공
init?(name: String, price: Double) {
guard price >= 0 else { return nil }
self.name = name
self.price = price
}
}
let validProduct = Product(name: "Laptop", price: 1200) // 성공
let invalidProduct = Product(name: "Phone", price: -50) // 실패, nil 반환
4. 필수 초기화: required키워드에 대해 알아봅시다
필수 생성자는 말 그대로 필수 생성자.
서브 클래스에서 반드시 구현해야 하는 생성자의 경우, 슈퍼 클래스에서 required init으로 설정해두는 것
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
UIView 분리할 때 맨날 자동으로 작성되던 요게 필수 생성자
class Human {
var name: String?
required init(name: String) {
self.name = name
}
}
Human이란 클래스 안에 required init(name:)이란 필수 생성자가 작성돼 있다.
그러면 이제 Human을 상속받는 모든 서브 클래스는 required init(name:)를 필수적으로 작성해야하겠네?
class Hanyeol: Human {
var nickName: String
init(nickName: String) {
self.nickName = nickName
super.init(name: "")
}
//erorr! 'required' initializer 'init(name:)' must be provided by subclass of 'Human'
}
맞다.
Human이란 클래스를 상속받는 Hanyeol 클래스의 경우, required initializer를 작성하지 않았다며 에러가 발생
따라서
class Hanyeol: Human {
var nickName: String
init(nickName: String) {
self.nickName = nickName
super.init(name: "")
}
required init(name: String) {
fatalError("init(name:) has not been implemented")
}
}
이런 식으로 Human 클래스에서 정의 했던 required init(name:)을 정의해주어야만 에러가 사라짐
엥? 그럼 Human클래스의 required init(name:)을 그대로 작성하는 거니까 이건 오버라이딩 아닌가??
근데 왜 오버라이딩 키워드를 안 붙이지??
결론적으론 오버라이딩이 맞다.
근데 required init 같은 경우, override 키워드 없이, 슈퍼 클래스와 동일한 형태로 구현해줘야 한다.
근데,
class Hanyeol: Human {
var nickName: String?
}
이렇게 모든 프로퍼티가 기본 값을 갖고 있어, 지정 생성자를 따로 작성하지 않으면 부모 클래스의 지정 생성자를 모두 상속받는다.
→ 이렇게 부모 클래스의 생성자를 모두 상속받을 경우에는, required init(name:)을 구현하지 않아도 에러가 안 난다.
부모의 모든 생성자를 상속받으면 부모에 선언되어 있는 required init(name:)을 상속받지만,
자식 클래스에서 지정 생성자를 구현해버릴 경우, 더이상 상속 조건에 맞지 않아 required init(name:)을 상속받을 수 없음.
따라서 이 땐 required init(name:)를 자식 클래스에서 필수로 정의해줘야 하는 것
즉, required init은 서브 클래스에서 "지정 생성자를 직접 구현"했을 경우에만 필수적
'SOPT > 문법 스터디' 카테고리의 다른 글
[SOPT] 문법 스터디 #8 에러처리와 확장 (3) | 2024.11.18 |
---|---|
[SOPT] 문법 스터디 #7 초기화 해제 (0) | 2024.11.17 |
[SOPT] 문법 스터디 #5 상속 (5) | 2024.11.15 |
[SOPT] 문법 스터디 #4 메서드와 옵셔널 체이닝 (8) | 2024.11.14 |
[SOPT] 문법 스터디 #3 열거형과 프로퍼티 (0) | 2024.11.13 |