초기화(Initialization)와 생성자(Initializer)
초기화(Initialization)
- 클래스, 구조체, 열거형의 인스턴스를 생성하는 과정이다
- 각 저장 속성에 대한 초기값을 설정하여 인스턴스를 사용 가능한 상태로 만드는 과정이다
- 열거형의 경우 저장 속성이 없으므로
case 중 하나를 선택하여 인스턴스를 생성한다
생성자(Initializer)
- 생성자(Initializer)의 역할은 인스턴스를 초기화하면서 모든 저장 속성이 유효한 초기값을 가지도록 보장하는 것이다
- 생성자가 완료되면 인스턴스는 사용 가능한 상태가 된다
생성자(Initializer) 구현
생성자를 구현하는 방법
- 모든 저장 속성을 초기값으로 설정해야 한다
- 저장 속성을 선언과 동시에 초기화하거나, 옵셔널로 선언할 수 있다 (옵셔널일 경우 기본값은
nil)
- 사용자 정의 생성자를 구현하면, 자동으로 제공되는 기본 생성자(
init())는 사라진다
- 구조체는 자동으로 제공되는 멤버와이즈 이니셜라이저를 사용 가능하다
클래스, 구조체, 열거형에서 동일하게 사용됨 (클래스는 상속이 존재하여 좀 더 복잡함)
생성자 구현의 기본 예제
기본적인 생성자 구현 (클래스)
class Laptop {
var brand: String
var model: String
var price: Double
var isOn: Bool
init(brand: String, model: String, price: Double, isOn: Bool) {
self.brand = brand
self.model = model
self.price = price
self.isOn = isOn
}
func powerOn() {
isOn = true
}
func powerOff() {
isOn = false
}
}
let myLaptop = Laptop(brand: "Apple", model: "MacBook Pro", price: 2499.99, isOn: false)
myLaptop.powerOn()
print(myLaptop.isOn)
기본값을 사용한 생성자 구현 (클래스)
class Phone {
var brand: String = "Unknown"
var model: String = "Unknown"
var batteryLevel: Int = 100
init(brand: String, model: String) {
self.brand = brand
self.model = model
}
}
let myPhone = Phone(brand: "Samsung", model: "Galaxy S21")
print(myPhone.brand)
print(myPhone.batteryLevel)
생성자의 오버로딩(Overloading)
- 오버로딩은 같은 이름의 메서드 또는 생성자를 여러 개 정의하되, 매개변수의 수, 타입, 순서가 다르게 정의하여 사용하는 것이다
- Swift의 생성자는 오버로딩이 가능하며, 다양한 방식으로 인스턴스를 초기화할 수 있게 해준다
생성자의 오버로딩
class Vehicle {
var type: String
var brand: String
var model: String
var speed: Double
init() {
self.type = "Unknown"
self.brand = "Unknown"
self.model = "Unknown"
self.speed = 0.0
}
init(type: String, brand: String) {
self.type = type
self.brand = brand
self.model = "Unknown"
self.speed = 0.0
}
init(type: String, brand: String, model: String, speed: Double) {
self.type = type
self.brand = brand
self.model = model
self.speed = speed
}
}
let unknownVehicle = Vehicle()
let bike = Vehicle(type: "Bike", brand: "Yamaha")
let car = Vehicle(type: "Car", brand: "Tesla", model: "Model S", speed: 250.0)
기본 생성자(Default Initializer)와 저장 속성의 기본값
기본 생성자(Default Initializer)
- 기본 생성자는 인스턴스의 모든 저장 속성에 기본값이 설정되어 있을 때 자동으로 제공되는 생성자이다
- 만약 저장 속성 중 하나라도 기본값이 없으면 기본 생성자가 자동으로 제공되지 않는다
기본 생성자가 자동으로 제공되는 경우
class User {
var username: String = "Guest"
var age: Int = 0
}
let user = User()
print(user.username)
print(user.age)
기본값이 없는 경우 - 직접 생성자 구현 필요
class Product {
var name: String
var price: Double
init(name: String, price: Double) {
self.name = name
self.price = price
}
}
let laptop = Product(name: "MacBook", price: 1299.99)
print(laptop.name)
print(laptop.price)
구조체의 멤버와이즈 이니셜라이저(Memberwise Initializer)
구조체의 특징
- 구조체는 클래스와 다르게 멤버와이즈 이니셜라이저를 자동으로 제공한다
- 구조체의 저장 속성에 기본값이 있으면 기본 생성자도 자동으로 제공된다
구조체의 멤버와이즈 이니셜라이저
struct Book {
var title: String
var author: String
var price: Double = 9.99
}
let book1 = Book(title: "Swift Programming", author: "Apple")
let book2 = Book(title: "Clean Code", author: "Robert Martin", price: 29.99)
구조체의 멤버와이즈 이니셜라이저가 제공되는 이유
- 구조체의 멤버와이즈 이니셜라이저는 구조체 사용을 더 편리하게 하기 위해 제공되는 것이다
- 클래스는 상속과 관련된 문제가 있을 수 있기 때문에 자동으로 제공되지 않는다
추가로 알아야 할 사항 (구조체 & 클래스의 차이점)
- 클래스는 참조 타입(Reference Type) 이며, 상속이 가능하다
- 구조체는 값 타입(Value Type) 이며, 상속이 불가능하다
- 구조체는 값 타입이므로 할당이나 전달 시 복사된다
- 클래스는 참조 타입이므로 인스턴스를 할당하거나 전달할 때 동일한 인스턴스를 참조한다
구조체 vs 클래스 생성자(Initializer)
구조체의 생성자
- 지정 생성자 (Designated Initializer) -
init()
- 실패 가능 생성자 (Failable Initializer) -
init?() 또는 init!()
- 멤버와이즈 생성자 (Memberwise Initializer) - 자동으로 제공됨 (구조체 전용)
클래스의 생성자
- 지정 생성자 (Designated Initializer) -
init()
- 편의 생성자 (Convenience Initializer) -
convenience init() (상속과 관련)
- 필수 생성자 (Required Initializer) -
required init() (상속과 관련)
- 실패 가능 생성자 (Failable Initializer) -
init?() 또는 init!()
구조체의 생성자 (지정 생성자)
지정 생성자 사용
struct Circle {
var radius: Double
var color: String
init() {
self.radius = 1.0
self.color = "White"
}
init(radius: Double, color: String) {
self.radius = radius
self.color = color
}
init(color: String) {
self.radius = 1.0
self.color = color
}
}
지정 생성자 간 호출 (self.init 사용)
struct Rectangle {
var width: Double
var height: Double
init() {
self.init(width: 1.0, height: 1.0)
}
init(width: Double, height: Double) {
self.width = width
self.height = height
}
}
클래스의 생성자 (지정 생성자와 편의 생성자)
클래스의 지정 생성자와 편의 생성자 사용
class Animal {
var species: String
var age: Int
init(species: String, age: Int) {
self.species = species
self.age = age
}
convenience init() {
self.init(species: "Unknown", age: 0)
}
}
지정 생성자(Designated Initializer)와 편의 생성자(Convenience Initializer)의 차이점
지정 생성자 (Designated Initializer)
- 모든 저장 속성을 초기화한다
- 클래스의 경우, 상위 클래스의 지정 생성자를 반드시 호출해야 한다 (
super.init())
- 지정 생성자는 인스턴스를 완전히 초기화하는 데 필요한 모든 정보를 제공해야 한다
- 지정 생성자는 동일 클래스 내에서 다른 지정 생성자를 호출할 수 있다
- 지정 생성자는 편의 생성자를 호출할 수 없다
편의 생성자 (Convenience Initializer)
- 지정 생성자를 호출하여 초기화를 위임한다 (
self.init())
- 모든 저장 속성을 직접 초기화하지 않아도 된다
- 편의 생성자는 동일 클래스 내에서 다른 지정 생성자를 호출하여 인스턴스를 생성한다
- 편의 생성자는 상위 클래스의 지정 생성자를 직접 호출할 수 없다
- 인스턴스를 간편하게 생성하는 역할을 담당한다
비교 예제: 지정 생성자 vs 편의 생성자
class Vehicle {
var brand: String
var speed: Int
init(brand: String, speed: Int) {
self.brand = brand
self.speed = speed
}
convenience init() {
self.init(brand: "Unknown", speed: 0)
}
}
class Car: Vehicle {
var model: String
init(brand: String, speed: Int, model: String) {
self.model = model
super.init(brand: brand, speed: speed)
}
convenience init(model: String) {
self.init(brand: "Generic", speed: 120, model: model)
}
convenience init() {
self.init(model: "Unknown Model")
}
}
Vehicle 클래스의 convenience init()은 지정 생성자인 init(brand:speed:)를 호출하여 초기화를 위임한다
Car 클래스는 Vehicle 클래스를 상속하며, 상위 클래스의 지정 생성자를 호출하기 위해 super.init()을 사용한다
- 편의 생성자
convenience init(model:)은 동일 클래스 내의 지정 생성자를 호출하여 초기화를 수행한다
- 편의 생성자는 상위 클래스의 지정 생성자를 직접 호출할 수 없다
- 상위 클래스의 편의 생성자는 서브클래스에서 자동으로 상속되지 않는다
생성자 위임 규칙 (Initializer Delegation Rules)
델리게이트 업 (Delegate Up)
- 서브클래스의 지정 생성자는 반드시 상위 클래스의 지정 생성자를 호출하여 초기화해야 한다 (
super.init())
- 상위 클래스의 지정 생성자를 호출하지 않으면 초기화가 완료되지 않는다
델리게이트 어크로스 (Delegate Across)
- 편의 생성자는 동일 클래스의 다른 지정 생성자를 호출하여야 한다 (
self.init())
- 편의 생성자는 궁극적으로 지정 생성자를 호출하여 초기화를 완료해야 한다
- 편의 생성자는 상위 클래스의 지정 생성자를 직접 호출할 수 없다
- 편의 생성자는 상위 클래스에서 상속되지 않는다
생성자 위임 규칙과 상속 관계
class Computer {
var brand: String
var ram: Int
init(brand: String, ram: Int) {
self.brand = brand
self.ram = ram
}
convenience init() {
self.init(brand: "Unknown", ram: 8)
}
}
class Laptop: Computer {
var batteryLife: Int
init(brand: String, ram: Int, batteryLife: Int) {
self.batteryLife = batteryLife
super.init(brand: brand, ram: ram)
}
convenience init(batteryLife: Int) {
self.init(brand: "Generic", ram: 16, batteryLife: batteryLife)
}
convenience init() {
self.init(batteryLife: 10)
}
}
Laptop 클래스는 Computer 클래스를 상속하며, 지정 생성자와 편의 생성자를 모두 가지고 있다
Laptop 클래스의 지정 생성자는 super.init()을 사용하여 상위 클래스의 지정 생성자를 호출한다 (델리게이트 업)
Laptop 클래스의 편의 생성자 convenience init(batteryLife:)는 동일 클래스의 지정 생성자를 호출하여 초기화를 수행한다 (델리게이트 어크로스)
- 모든 편의 생성자는 지정 생성자를 반드시 호출해야 한다
- 편의 생성자는 상위 클래스의 지정 생성자를 직접 호출할 수 없다
2단계 초기화 과정 (클래스의 인스턴스 생성 과정)
1단계 (필수)
- 해당 클래스에서 선언한 모든 저장 속성의 값을 초기화한다
- 상위 지정 생성자를 호출하여 상위 클래스의 저장 속성들을 초기화한다
- 모든 상위 클래스의 저장 속성 초기화가 완료되면 인스턴스는 완전히 초기화된 상태가 된다
2단계 (선택)
- 인스턴스 초기화가 완료되면, 모든 생성자가 인스턴스를 추가로 설정할 수 있다
- 초기화가 완료된 이후에
self 속성에 접근이 가능해지고, 인스턴스의 메서드를 호출할 수 있다
2단계 초기화 과정 구현 예제
class Appliance {
var brand: String
init(brand: String) {
self.brand = brand
}
}
class Refrigerator: Appliance {
var temperature: Double
init(brand: String, temperature: Double) {
self.temperature = temperature
super.init(brand: brand)
}
convenience init() {
self.init(brand: "Unknown", temperature: 5.0)
}
func setTemperature(to newTemperature: Double) {
self.temperature = newTemperature
}
}
let fridge = Refrigerator()
fridge.setTemperature(to: -2.0)
let fancyFridge = Refrigerator(brand: "LG", temperature: 4.0)
fancyFridge.setTemperature(to: -5.0)
생성자 규칙 요약 및 정리
지정 생성자 (Designated Initializer)
- 모든 저장 속성을 초기화한다
- 클래스의 경우
super.init()을 호출하여 상위 클래스의 지정 생성자를 호출해야 한다
- 지정 생성자는 다른 지정 생성자를 호출할 수 있지만, 편의 생성자를 호출할 수 없다
편의 생성자 (Convenience Initializer)
- 지정 생성자를 호출하여 초기화를 위임한다 (
self.init())
- 모든 저장 속성을 초기화할 필요는 없다
- 상위 클래스의 지정 생성자를 직접 호출할 수 없다
- 지정 생성자 호출을 단순화하고 인스턴스 생성을 더 편리하게 만들어준다
델리게이트 규칙 요약 (Initializer Delegation)
델리게이트 업 (Delegate Up)
- 서브클래스의 지정 생성자는 반드시 상위 클래스의 지정 생성자를 호출해야 한다 (
super.init())
델리게이트 어크로스 (Delegate Across)
- 편의 생성자는 동일 클래스의 다른 지정 생성자를 호출하여야 한다 (
self.init())
- 편의 생성자는 궁극적으로 지정 생성자를 호출하여 초기화를 완료해야 한다