초기화
- 인스턴스를 생성하는 과정
- 저장 속성에 대한 초기값을 설정하여 사용가능한 상태가 되는 것
- 즉, 생성자 실행의 종료시점에는 모든 저장 속성에 값이 저장되어 있어야 함
- 오버로딩 지원
- 파라미터의 수, 아규먼트 레이블, 자료형으로 구분
class Foden {
var name: String
...
init(name: String, weight: Int) {
self.name = name
self.weight = weight
}
...
init(name: String) {
self.init(name: name, weight: 80.0)
}
}
- 클래스, 구조체, (열거형) 동일
- 기본 생성자 : 저장 속성의 기본값을 설정하면 “자동” 구현이 제공
- = 생성자를 아무것도 만들지 않으면 저장 속성 초기화 시 문제 없음(자동 구현됨)
- 초기화의 방법
- 저장 속성의 선언과 동시에 값을 저장
- 저장 속성을 옵셔널로 선언 (초기값이 없어도 nil로 초기화)
- 생성자에서 값을 초기화
- 반드시 생성자를 정의해야만 하는 것은 아님
- 컴파일러는 기본 생성자(default initializer)를 자동으로 생성 → init()
- 생성자 구현 시 자동 생성 X
- 멤버와이즈 이니셜라이저(Memberwise Initializer)
- 구조체의 특별한 생성자
- 구조체는 멤버와이즈 이니셜라이저 자동 제공
- 저장 속성들이 기본값을 가지고 있더라도, 추가적으로 멤어와이즈 이니셜라이저를 제공
- 개발자가 직접적으로 생성자 구현 시, 멤버와이즈 이니셜라이저 자동 제공 X
- 왜 굳이..?
- 클래스보다 더 편하게 쓰기 위함일 것으로 추측
- 클래스의 경우에는 생성자가 상속하고 관련이 있기 때문에 복잡하기 때문에 구현 X
struct Rgb {
var red: Double
var green: Double
var blue: Double
}
var c1 = Rgb(red: 20.0, green: 10.0, blue: 15.0)
// Memberwise : 모든 멤버에 관련된
생성자
구분 | 구조체 | 클래스 |
지정 생성자 | init() { } init(파라미터) { } |
init() { } init(파라미터) { } |
편의 생성자 | X | convenience init(파라미터) { } |
필수 생성자 | X | required init(파라미터) { } |
실패가능 생성자 | init?(파라미터) { } init!(파라미터) { } |
init?(파라미터) { } init!(파라미터) { } |
소멸자 | X | deinit { } |
구조체 생성자
struct Color1 {
let red, green, blue: Double
init() { // 구조체는 다른 생성자를 호출하는 방식도 가능 ⭐️
//red = 0.0
//green = 0.0
//blue = 0.0
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
init(white: Double) { // 구조체는 다른 생성자를 호출하는 방식도 가능 ⭐️
//red = white
//green = white
//blue = white
self.init(red: white, green: white, blue: white)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
// 값타입(구조체)의 경우 자체 지정생성자 작성할 때
// 생성자 내에서 self.init(...)를 사용하여 다른 이니셜라이저를 호출하도록 할 수 있음
클래스 생성자
- 생성자에서 다른 생성자를 부를 땐 앞에 convenience를 붙어야 함
- 편의 생성자는 편리하게 생성하기 위한 서브 생성자라고 보면됨.(메인아님)
- (메인) 지정 생성자에 의존하는 방식 (지정 생성자 호출)
- 지정 생성자는 모든 속성을 초기화 해야함.
- 편의 생성자는 모든 속성을 초기화 할 필요가 없음(편의적 성격)
- 초기화 과정을 조금 간편하게, 상속관계에서 개발자가 실수 할 수 있는 포인트를 배제하기 위한 생성자
- (이미 모든 속성을 초기화하는 지정생성자가 있다면) 모든 속성을 초기화하지 않는 경우 편의생성자로 구현을 권장
class Color2 {
let red, green, blue: Double
convenience init() { // 생성자에서 다른 생성자를 부를 떈 앞에 convenience를 붙여야 함.
self.init(red: 0.0, green: 0.0, blue: 0.0)
//self.init(white: 0.0)
}
convenience init(white: Double) {
//self.red = white -> 올바른 구현이라고 할 순 없음. -> 상속 시 재정의할 내용이 많아짐.
//green = white
//blue = white
self.init(red: white, green: white, blue: white)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
- 클래스의 상속, 지정/편의 생성자 사용 예시
class Aclass {
var x: Int
var y: Int
init(x: Int, y: Int) { // 지정생성자 - 모든 저장 속성 설정
self.x = x
self.y = y
}
convenience init() { // 편의생성자 - (조금 편리하게 생성) 모든 저장 속성을 설정하지 않음
self.init(x: 0, : 0)
}
}
// 상속 시
class Bclass {
var z: Int
// 지정 생성자는 반드시 재정의
init(x: Int, y: Int, z: Int) { // 실제 메모리에 초기화 되는 시점
self.z = z // ⭐️ (필수) - 나의 저장 속성 값 세팅
//self.y = y // 불가 (메모리 셋팅 전) - 아직 y라는 데이터 공간이 생기지 않았기 때문
super.init(x: x, y: y) // ⭐️ (필수) 상위의 지정생성자 호출
//self.z = 7
//self.y = 7 이미 메모리 공간을 만들어 놓은 뒤 변경하는 것은 가능
//self.doSomething() - 붕어빵 틀이 생성된 이후엔 메서드를 실행하는 것이 가능
}
// 편의 생성자는 상속되지 않음 -> 동일한 이름을 가진 '편의 생성자' 생성 가능
convenience init(z: Int) {
//self.z = 7 //==========> self에 접근불가
self.init(x: 0, y: 0, z: z) // 지정생성자가 찍어내기 전까진 접근할 수 없음.
//self.z = 7
}
convenience init() {
self.init(z: 0)
}
func doSomething() {
print("Do something")
}
}
지정생성자
- init(…) 형태를 가짐
- 모든 속성을 초기화해야함
- 저장속성 선언, 동시에 값을 저장하거나, 저장 속성을 옵셔널 타입으로 선언하는 것도 가능
- 모든 저장 속성이 초기화 되는 경우, 기본생성자 자동 제공 → init()
- 생성자를 1개이상 구현하면 기본 생성자를 제공하지 않음
- 상속 시 반드시 재정의 해야함
편의생성자
- 상속과 관련
- 서브 개념의 생성자
- 지정 생성자에 의존 및 호출(지정생성자가 저장 속성 초기화)
- 초기화 과정을 간편하게 하기 위함
- 상속했을때, 자식클래스에서 재정의를 못함
- 서브클래스의 지정생성자는 상위 지정생성자를 반드시 호출, 편의 생성자는 반드시 자기 단계에 있는 지정생성자를 호출
- (참고) : 구조체(열거형)의 경우, convenience 키워드를 붙이지 않아도 다른 생성자를 호출 가능
- 편의생성자는 편의생성자를 호출할 수 있음(단, 같은 클래스 내)
- 생성자 위임 규칙
- 델리게이트 업(delegate up)
- 서브클래스의 지정생성자는 수퍼 클래스의 지정생성자를 반드시 호출해야 함
- 델리게이트 어크로스(delegate across)
- 편의생성자는 동일한 클래스에서 다른 이니셜 라이저를 호출, 궁극적으로 지정생성자를 호출
- 인스턴스 메모리 생성에 대한 규칙이 존재
- 안 지키면 올바르게 초기화 X
- 델리게이트 업(delegate up)
상속과 초기화
- 상속 관계에서 생성자 위임 규칙
- 메모리 초기화 (값 설정)
- 커스텀 설정 (설정 값을 바꾸거나, 메서드 실행)
- Swift는 하위에 있는 메모리부터 찍어낸 후에 상위에 있는 걸 찍어낸다.
- 자식클래스에서 자식 클래스의 저장 속성을 초기화한 뒤, 부모클래스 저장 속성 값을 초기화한다.
- Heap에서 다 찍어낸 이후 메모리 주소를 스택에 넘겨준다.
지정생성자 / 편의생성자 상속과 재정의 규칙
- 생성자
- 기본적으로 상속되지 않고 재정의 원칙
- 모든 저장 속성을 초기화하는 도구이기 때문에, 서브클래스에 최적화 안되어 있음
- 기본적으로 상속되지 않고 재정의 원칙
- 원칙
- 상위 지정생성자, 현재 단계의 저장 속성을 고려해서 구현🌕
- 1단계
- (상위)지정생성자 : 재정의 필수 고려
- 하위클래스에서 지정 생성자로 구현(재정의)
- 하위클래스에서 편의 생성자로 구현 가능(재정의)
- 구현 안해도됨(반드시 재정의하지 않아도 됨)
- (예외적 자동 상속)
- (상위)편의생성자 : 재정의 불가(호출불가)
- (예외적 자동 상속)
- (상위)지정생성자 : 재정의 필수 고려
- 2단계
- (현재단계의) 모든 저장 속성 초기화 및 상위의 지정 생성자 호출
- (편의생성자 구현시) 지정생성자를 호출
- E.g
class Player {
var name: String
var weight: Double
init(name: String, weight: Double) {
self.name = name
self.weight = weight
}
convenience init() {
self.init(name: "Foden", weight: 70.0)
}
}
// 상위 클래스
// init(name: String, weight: Double)
class FootBallPlayer: Player {
var position: String
// 지정생성자로 재정의
// override init(name: String, weight: Double) {
// self.position = "Bench"
// super.init(name: name, weight: weight)
// }
// 서브클래스에서 편의생성자로 구현
override convenience init(name: String, weight: Double) {
self.init(name: name, weight: weight, position: "Bench")
}
// (현재단계의) 생성자 구현
init(name: String, weight: Double, position: String) {
self.position = position
super.init(name: "Foden", weight: 80.0)
}
}
// 상위 크래스
// init(name: String, weight: Double)
// init(name: String, weight: Double, position: String)
class ManCityPlayer: FootBallPlayer {
var weeklyPay: Double
// 지정생성자로 재정의
override convenience init(name: String, weight: Double, position: String) {
self.init(name: name, weight: weight, position: position, weeklyPay: 10.0)
}
init(name: String, weight: Double, position: String, weeklyPay: Double) {
self.weeklyPay = weeklyPay
super.init(name: name, weight: weight, position: position)
}
}
var me = ManCityPlayer(name: "Park", weight: 76.0)
print(me.weeklyPay)
클래스의 지정, 편의 생성자 상속의 예외사항
- 지정생성자 자동상속
- 저장속성 기본값 설정 및 어떤 재정의도 안했을때
- 새 저장 속성이 아예 없거나, 기본값이 설정되어 있다면
- 부모클래스의 지정생성자 모두 자동 상속(하위에서 어떤 재정의도 하지 않으면)
- 편의생성자 자동상속
- 상위 지정생성자를 모두 상속하는 경우
- (위의) 지정생성자 모두 자동상속의 경우
- 상위의 지정생성자 모두 재정의하는 경우
- (결국, 모든 지정생성자를 상속하는 상황이 되면 편의생성자는 자동으로 상속됨)
- 상위 지정생성자를 모두 상속하는 경우
class Person {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
class Student: Person {
var age: Double
init(name: String, age: Double) {
self.age = age
super.init(name: name)
}
// 상위 지정생성자를 다 상속해서 재정의해서 구현할 시 -> 편의생성자도 상속
override convenience init(name: String) {
self.init(name: name, age: 25)
}
// convenience init() { } // 자동 상속(예외 규칙)
}
let foden = Student()
print(foden.name)
필수생성자
- 상속과 관련
- 클래스의 생성자 앞에 “requried” 키워드를 붙이면 하위클래스에서 반드시 해당 생성자를 구현해야 함
- 해당 생성자의 의미(파라미터 이름이 및 타입이 동일한)
- 하위클래스에서 필수 생성자 구현시, override(재정의) 키워드 없이 required(필수의) 키워드만 붙이면 됨
- 필수생성자 자동상속 조건
- 다른 지정 생성자를 구현 안하면, 자동으로 필수생성자 상속
class Aclass {
var x: Int
required init(x: Int) {
self.x = x
}
}
// 클래스 생성자 앞에 required(요구된/필수의) 키워드 사용시
// 하위 생성자는 반드시 해당 필수 생성자를 구현 해야함
class Bclass: Aclass {
// required init(x: Int) {
// super.init(x: x)
// }
// 만약 다른 생성자 구현시 반드시 required 필수 생성자 구현
// 현재는 아무것도 구현안되어 있으므로 자동 상속
}
var name = Bclass(x: 10)
print(name.x)
- 사용 예시
required init?(coder: aDecoder: NSCoder) {
fatalError("init(Coder:) has not been implemented")
}
실패가능 생성자
- 인스턴스 생성시, 실패가능성을 가진 생성자
- 인스턴스 생성 실패시 nil 리턴
- 생성자에 “?”을 붙여서, init?(…)으로 정의
- 다만, 오버로딩으로 인한 구분이 안되므로, 해당이름을 가진 생성자는 유일한 생성자여야함
- 실패불가능 생성자는 다른 실패가능 생성자를 호출 불가능 ⭐️
- (동일단계 - 델리게이트 어크로스)
- 실패가능 ===> 실패불가능 (호출/위임) (OK)
- 실패불가능 ===> 실패가능 (호출/위임) (X)
- (상속관계 - 델리게이트 업)
- (상위)실패가능 <=== (하위)실패불가능 (호출/위임) (X)
- (상위)실패불가능 <=== (하위)실패가능 (호출/위임) (OK)
- init! 생성자는 init?과 유사하게 취급하면 됨
class Player {
let name: String
// 실패가능 생성자
init?(name: String) {
if name.isEmpty {
print("nil")
return nil
}
self.name = name
}
//init(name: String) { } -> Error
}
let a = Player(name: "")
- 열거형에서의 활용
enum TemperatureUnit {
case kelvin
case celsius
case fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = TemperatureUnit.kelvin
case "C":
self = TemperatureUnit.celsius
case "F":
self = TemperatureUnit.fahrenheit
default:
return nil
}
}
}
let f: TemperatureUnit? = TemperatureUnit(symbol: "F")
print(f) // Optional(TemperatureUnit.fahrenheit)
enum Number: Int {
case one = 1
case two = 2
case three = 3
}
// 원시값이있는 열거형은 자동으로 실패가능 생성자 init?(rawValue :)를 구현함 ==> 일치하면 인스턴스 생성, 아니면 nil
let n1: Number? = Number(rawValue: 1)
print(n1) // 출력: Optional(Number.one)
- 상속에서의 예제
class Product {
let name: String
init?(name: String) { // 실패가능생성자
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil } // 상품의 갯수가 1보다 작으면 ====> 카트 항목 생성 실패
self.quantity = quantity // 수량이 한개 이상이면 ====> 초기화 성공, 여기서 역할을 다함
super.init(name: name) // "" (빈문자열이면) ====> 실패 가능 위임 OK
}
}
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("아이템: \(twoSocks.name), 수량: \(twoSocks.quantity)")
}
- (상속관계에서) 재정의
- 범위로 따져보기(실패가능 생성자의 범위가 더 큼)
class Document {
var name: String?
init() {} // 실패 불가능
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class AutoDocument: Document {
// 재정의 (상위) 실패불가능 => (하위) 실패불가능
override init() {
super.init()
self.name = "[Untitled]"
}
// 재정의 (상위) 실패가능 => (하위) 실패불가능
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}
class UntitledDocument {
override init() {
// super.init()
// 강제 언래핑(!)으로 구현 - 논리구조상 nil이 나올 수 없기 때문에 강제언래핑
super.init(name: "[Untitled]")!
}
}
- 실패가능 생성자를 init!(파라미터)로 정의
init?
과init!
의 역할:init?
: 실패 가능한 이니셜라이저. 초기화 과정에서 실패할 수 있고, 실패 시nil
을 반환.init!
: 실패 가능한 이니셜라이저. 실패 시 런타임 오류 발생.
init?
과init!
간의 위임 가능성:init?
에서init!
로, 그리고init!
에서init?
로 위임 가능.
init
과init!
간의 위임 가능성:- 실패 가능성이 있는
init!
에서 실패 가능성이 있는init
으로 위임 가능.
- 실패 가능성이 있는
class MyClass {
var value: Int
// 실패 가능한 이니셜라이저
init?(value: Int) {
guard value > 0 else {
return nil // 실패 시 nil 반환
}
self.value = value
}
// 실패 가능한 이니셜라이저에서 init!으로 위임
convenience init!(stringValue: String) {
guard let intValue = Int(stringValue) else {
self.init(value: 1)! // 실패 시 1로 초기화하고 강제 언래핑
return
}
self.init(value: intValue)
}
}
// 사용 예시
if let obj1 = MyClass(value: 5) {
print(obj1.value) // 출력: 5
}
let obj2 = MyClass(stringValue: "10")
print(obj2?.value) // 출력: 10
소멸자
- 인스턴스 해제시, 정리가 필요한 내용을 정의
- 클래스에서 최대 1개만 존재
- 인스턴스가 메모리에서 제거되기 직전에 자동으로 호출되는 메서드 부분
class Aclass {
var x = 0
var y = 0
deinit {
print("인스턴스의 소멸 시점")
}
}
반응형
'iOS > Swift' 카테고리의 다른 글
Swift No.19 (1) | 2024.01.25 |
---|---|
Swift No.18 (1) | 2024.01.23 |
Swift No.16 (0) | 2024.01.17 |
Swift No.15 (0) | 2024.01.17 |
Swift No.14 (0) | 2024.01.16 |