swift의 기초를 공부한 것을 기록하기 위해 작성 했습니다.
참고: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/
Documentation
docs.swift.org
참고 서적: 스위프트 프로그래밍(3판) - 야곰
Initialization
- 초기화는 클래스, 구조체 또는 열겨형의 인스턴스를 사용할 수 있도록 하는 준비과정이다.
- 초기화 과정에서 인스턴스에 저장된 각 프로퍼티에 대한 초기 값을 설정하고 인스턴스를 사용하기 전 기타 설정들을 한다.
- 초기화가 완료된 인스턴스는 사용 후 소멸 시점이 오면 소멸한다.
- objc 의 init과 다르게 swift의 init은 값을 반환하지 않는다
- initializer는 해당 타입의 새로운 인스턴스를 생성하기 위해 호출한다.
class SomeClass {
init() {
//초기화할 때 필요한 코드
}
}
struct SomeStruct {
init() {
// 초기화 코드
}
}
enum SomeEnum {
case someCase
init() {
//열거형은 초기화할 때 반드시 case중 하나가 되어야 한다.
self = .someCase
//초기화 코드
}
}
- 클래스의 지정 이니셜라이저는 익스텐션에서 구현해줄 수 없다. → extension봐야 알 것 같다.
setting initial values for stored properties
- 클래스와 구조체는 인스턴스가 생성될 때까지 모든 저장 프로퍼티에 적절한 초기값으로 할당해야 한다.
→ 구조체는 타입만 명시하면 되는 거 아닌가?
→ 구조체는 구조체 자체를 작성할 때는 명시하지 않아도 상관이 없다. 하지만 인스턴스를 생성할때는 무조건 할당해야 한다.
→ 하단의 Memberwise Initializers for sturcture types를 살펴보면 된다. - initializer의 내부에서 저장 프로퍼티의 초기값을 설정하거나 프로퍼티 정의의 일부로 기본 프로퍼티 값을 할당하여 설정할 수 있다.
- 프로퍼티에 기본값을 할당하거나 이니셜라이저 내에서 초기값을 설정하면 프로퍼티 옵저버를 호출하지 않고도 해당 프로퍼티의 값이 직접 설정된다.
초기값을 할당하는 init()의 예시)
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
// 초기값 설정
}
}
var f = Fahrenheit()
print("The default temperature is \\(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"
프로퍼티가 항상 동일한 초기값을 사용하는 경우 이니셜라이저 내에서 값을 설정하는 대신 기본값을 사용하자 결과는 동일하지만 기본값을 사용하면 프로퍼티의 초기화가 선언에 더 가깝게 연결된다.
→ 이니셜라이저를 짧고 명확하게 만들 수 있다. 기본값에서 프로퍼티의 유형을 유추하는 것이 가능해진다. 기본값을 사용하면 default initializer와 initializer의 상속을 더 쉽게 활용할 수 있다.
다음과 같은 방식으로 기본값을 설정해주면 된다.
struct Fahrenheit {
var temperature = 32.0
}
Custom initializer
- 이니셜라이저도 매개 변수를 가질 수 있다.
- 메서드의 매개 변수와 동일한 기능과 구문을 가진다.
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0
// 함수의 매개변수 처럼 값을 받아온다.
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0
이렇게 custom initializer를 만들면 기존의 기본 init() (자동으로 생성된 init)은 별도로 구현하지 않는 이상 사용이 불가능해진다.
parameter Names and Argument Labels
- initializer에는 함수와 메서드처럼 괄호 앞에 식별 이름이 없다. 그렇기 때문에 매개변수 이름과 타입이 어떤 initializer를 호출해야 하는지 식별하는데 특히 중요하다.
- → func 함수이름() init(매개변수 이름)
- swift는 initializer에 모든 매개변수에 대해 사용자가 지정하지 않은 경우 자동 인수 label을 제공한다.
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
위에 선언한 init을 다음과 같이 사용하는 것이 가능하다.
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
- argument에 이름이 정의되어 있는 경우 사용하지 않고 init을 하는 것은 불가능하다.
let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required
Initializer parameters without argument labels
- init의 매개변수에 argument 이름을 사용하지 않으려면 해당 매개변수 앞에 _ 를 작성해야 한다.
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
// 다음과 같이 작성하면 init을 할때 Celsius(celsius: 37.0) 이렇게 작성을 안하고 Celsius(37.0) 이렇게 작성해도 된다.
init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 37.0
- 의도가 명확한 경우에만 사용을 권장한다.
Optional property type
- 초기화 과정에서 값을 지정해주기 힘든 경우 optional 타입으로 선언한다
- 프로퍼티에 optional 타입이 있는 경우
- 초기값을 설정하지 않는 경우 자동으로 nil로 초기화가 진행된다.
class SurveyQuestion {
var text: String
var response: String? // 초기화 과정에서 자동으로 nil이 들어간다.
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
print(cheeseQuestion.response) // nil
cheeseQuestion.response = "Yes, I do like cheese."
상수 프로퍼티 (Assiging Constant Properties During Initialization)
- 초기화 중 상수 프로퍼티에 값을 할당하면 더 이상 수정이 불가능하다.
class SurveyQuestion {
//상수
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// Prints "How about beets?"
beetsQuestion.text = "hello"
//Error: Cannot assign to property: 'text' is a 'let' constant
beetsQuestion.response = "I also like beets. (But not with cheese.)"
클래스 인스턴스의 상수 프로퍼티는 프로퍼티가 정의된 클래스에서만 초기화할 수 있습니다. 해당 클래스를 상속받은 자식 클래스의 initializer에서는 부모 클래스의 상수 프로퍼티 값을 초기화 할 수 없다.
Default Initializer
- swift는 모든 프로퍼티에 기본값이 존재하고 다른 initializer를 제공하지 않는다면 기본 init을 제공한다.
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
- 위와 같이 모든 프로퍼티에 기본값을 가지고 다른 init이 없기 때문에 기본 init을 자동으로 얻는다.
Memberwise Initializers for structure types
- 구조체는 custom init을 정의하지 않은 경우 자동으로 memberwise init을 제공받는다.
- memberwise라고 부르는 이유는 클래스나 구조체 내부의 프로퍼티와 함수를 멤버라고 부르기 때문이다.
- 기본 init과는 다르게 구조체에는 기본값이 없는 저장 프로퍼티가 있더라도 멤버와이즈 이니셜라이저를 받는다.
class는 memberwise init을 제공하지 않는다.
→ 아마 상속 때문에 그럴 수 있다고 생각한다. 상속을 받으면 프로퍼티가 계속 늘어나기 때문에 자동적으로 init을 제공하는 것이 불가능해진다고 생각한다.
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
- memberwise init을 호출할 때는 기본값이 있는 프로퍼티의 값을 생략할 수 있다.
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// Prints "0.0 2.0"
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// Prints "0.0 0.0"
Initializer Delegation for Value Types ( 초기화 위임)
- 값 타입의 구조체와 열거형은 코드의 중복을 피하기 위해 이니셜라이저가 다른 이니셜라이저에게 일부 초기화를 위임하는 초기화 위임을 간단하게 구현이 가능하다.
- 값 타입에서 다른 이니셜라이저가 다른 이니셜라이저를 호출하려면 self.init을 사용해야한다.
- 이니셜라이저 내부에서만 self.init이 사용이 가능하다.
- custom init을 사용하면 기본 init을 사용할 수 없기 때문에 최소 2개 이상의 init을 정의해야한다.
custom init을 정의할 때 기본 init이나 memberwise init을 사용하고 싶다면 익스텐션을 사용하여 custom init을 구현하면된다.
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
//첫 번째 init을 이용한 초기화
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)
//두 번째 위임을 이용한 초기화
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
- 열거형 예시)
enum Student {
case elementary, middle, high
case none
// 사용자 정의 이니셜라이저가 있는 경우, init() 메서드를 구현해주어야
// 기본 이니셜라이저를 사용할 수 있다.
init() {
self = .none
}
// 첫 번째 init
init(koreanAge: Int) {
switch koreanAge {
case 8...13:
self = .elementary
case 14...16:
self = .middle
case 17...19:
self = .high
default:
self = .none
}
}
// 두 번째 init
init(bornAt: Int, currnetYear: Int) {
self.init(koreanAge: currnetYear - bornAt + 1)
}
}
var younger: Student = Student(koreanAge: 16)
print(younger) // middle
younger = Student(bornAt: 1998, currnetYear: 2016)
print(younger) //high
Class Inheritance and Initailization
- class가 상위 클래스에게 상속받는 프로퍼티를 포함하여 클래스의 모든 저장 프로퍼티는 초기화 시 초기값을 할당해야 한다.
- swift는 클래스 유형에 대해 두 가지 종류의 이니셜라이저를 정의
- designated initializer
- convenience initializer
Designated initializer
- Designated initializer는 클래스의 기본 이니셜라이저
- 모든 클래스에는 designates init이 하나 이상 있어야 한다.
- 해당 클래스가 가지는 모든 프로퍼티를 완전히 초기화하고 적절한 super클래스 이니렬라이저를 호출하여 super 클래스 chain까지 초기화를 진행한다.
- 클래스에서 Designated init이 거의 없는 경우가 많으며, 한 클래스에 이니셜라이저가 하나만 있는 경우도 흔하다.
- 모든 초기화 호출이 designated init을 통과한다.
- Initializer Delegation for Class Types를 보면 알 수 있다.
class Vehicle {
var numberOfWheels: Int
// 이것이 designated 초기화 메서드
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
}
class Car: Vehicle {
var numberOfDoors: Int
// Car 클래스의 designated 초기화 메서드
init(numberOfWheels: Int, numberOfDoors: Int) {
self.numberOfDoors = numberOfDoors
super.init(numberOfWheels: numberOfWheels)
}
}
Convenience initializer
- convenience init은 클래스에 대한 이니셜라이저를 지원하는 보조 이니셜라이저다.
- convenience init을 정의하여 동일한 클래스에서 designated init을 호출하고 designated init의 파라미터 일부를 기본값으로 설정할 수 있다.
- 특정 사용 사례 또는 입력 값 유형에 대해 클래스의 인스턴스를 생성하는 convenience init을 정의할 수 있다.
- 클래스에서 필요하지 않으면 제공할 필요가 없다.
- 클래스의 초기화 의도를 더 명확하게 할 수 있는 경우 사용하면 좋다.
class Person {
var name: String
var age: Int
// designated init
init(name: String, age: Int) {
self.name = name
self.age = age
}
// convenience init
convenience init(name: String) {
self.init(name: name, age: 0)
}
}
let baby = Person(name: "John")
// 'John'라는 이름을 가진 0세의 Person 객체를 생성
Initializer Delegation for Class Types
- Deisignated init과 convenience init의 관계를 단순화 하기 위해서 다음과 같은 규칙을 사용
규칙 1
Designated init은 반드시 super class 의 Designate init을 호출해야 한다.
init(numberOfWheels: Int, numberOfDoors: Int) {
self.numberOfDoors = numberOfDoors
super.init(numberOfWheels: numberOfWheels)
}
규칙 2
Convenience init은 같은 클래스에서 다른 이니셜라이저를 호출해야 한다.
convenience init(name: String) {
self.init(name: name, age: 0)
}
규칙 3
Convenience init은 최종적으로 designated init을 호출해야 한다.
- 다음과 같은 규칙들은 사진을 보면 이해가 잘된다.
- 가장 상단에 conveniece 가 다른 convenience를 호출하고 그 convenience가 designated 를 호출하여 규칙 2,3을 만족한다.
- 가장 상단 class의 supre class가 없기 때문에 규칙 1은 적용이 안된다.
- 하단의 subclass는 convenience에서 Designated를 호출하고 최종적으로 Designated를 호출한다.
- 규칙 2,3을 만족
- 모든 designated가 super class의 designated를 호출 → 규칙 1 만족
이러한 규칙은 클래스의 인스턴스를 만드는 방법에 영향을 미치지 않는다 이니셜라이저 구현에만 영향을 준다
Two - Phase Initialization
- swift는 class의 초기화는 2단계로 진행된다.
- 첫 번째 단계는 각 저장 프로퍼티를 가지는 클래스에서 초기 값을 할당한다.
- 모든 저장 프로퍼티가 초기 상태가 결정되면 두 번째 단계가 시작된다.
- 각 클래스는 새 인스턴스를 사용할 준비가 된 것으로 간주되기 전에 저장 프로퍼티를 추가로 커스터마이징 할 수 있는 기회를 얻게 된다.
- 왜 이렇게 할까?
- 2단계 초기화를 진행하면 안전하면서 클래스 계층 구조의 각 클래스에 완전한 유연성을 부여한다.
- 2단계 초기화는 속성 값이 초기화되기 전에 접근을 방지한다.
- 다른 이니셜라이저에 의해 속성 값이 다른 값으로 설정되는 것을 방지한다.
Swift의 2단계 초기화 프로세스는 Objective-C의 초기화와 유사하다고 한다.
가장 큰 차이점은 1단계에서 Objective-C는 모든 프로퍼티에 0 또는 null값(예: 0 또는 nil)을 할당한다.
Swift의 초기화 흐름은 사용자 지정 초기값을 설정할 수 있고 0 또는 nil이 유효한 기본값이 아닌 유형에 대처할 수 있다는 점에서 더 유연하다.
swift의 컴파일러는 4가지 Safety check을 수행하여 two phase 초기화가 오류없이 완료되도록 한다.
Safety check 1
- Designated init은 super class에 위임하기 전에 해당 클래스의 모든 프로퍼티를 초기화 했는지 확인해야한다.
- 객체에 대한 메모리는 저장된 모든 프로퍼티의 초기 상태를 알 수 있을때만 완전히 초기화된 것으로 간주된다.
Safety check 2
- Designated init은 상속된 프로퍼티에 값을 할당하기 전에 super class의 이니셜라이저를 호출해야 한다. 그렇지 않으면 designated가 초기화 한 값의 일부를 super class가 값을 덮어 쓰게 된다.
Safety check 3
- convenience init은 프로퍼티에 값을 할당하기 전에 다른 이니셜라이저를 호출해야한다.
- 그렇지 않으면 convenience init가 할당하는 새 값이 designated init에 의해 덮어써져 원하는 값으로 초기화를 못한다.
Safety check 4
- initializer는 초기화의 첫 번째 단계(모든 프로퍼티가 값을 초기화 하는 상황) 가 완료될 때까지 인스턴스 메서드를 호출하거나, 인스턴스 프로퍼티의 값을 읽거나, 자기를 값으로 참조할 수 없다.
→ 초기화 안하면 사용 못한다
→ 첫 번째 단계가 끝날 때 인스턴스가 유효한 경우에만 접근이 가능
class up {
var name: String
init(name: String) {
self.name = name
}
}
class down: up {
var age: Int
init(name: String, age: Int) {
self.age = age
super.init(name: name)
// super.init이 self보다 위에 있으면 Error가 나온다.
}
}
→ 하위 클래스의 공간을 확보하고 상위로 향하는게 아닌가 생각된다.
Phase 1
- 클래스에서 designated init 또는 conveniece init을 호출한다
- 클래스의 새 인스턴스에 대한 메모리가 할당된다. 메모리는 아직 초기화 안됨
- 클래스에 대한 designated init은 해당 클래스의 모든 저장 프로퍼티에 값이 있는지 확인한다.
- 저장 프로퍼티에 대한 메모리가 초기화 된다.
- Designated init은 super class에게 위임해 자체 저장 프로퍼티에 동일한 작업을 수행하도록 한다.
→ 하위에서 했으니 super의 프로퍼티들에 대해 동일한 작업을 하는 듯 하다. - 이 작업은 클래스 상속 체인의 최상위에 도달할 때까지 계속된다.
→ 방향이 하위에서 상위로 가는 이유는 하위 클래스는 누구를 상속 받았는지 알 수 있지만 , 상위 클래스에서는 누구에게 상속을 했는지 모르기 때문 인 것 같다. - chain 가장 상단에 도달하고 chain의 마지막 클래스의 저장된 모든 프로퍼티에 값을 갖도록 하면 인스턴스의 메모리가 완전히 초기화되고 1단계가 완료된 것으로 간주한다.
Phase 2
- 체인의 위쪽에서 아래로 내려오면서 각 init이 인스턴스를 추가로 커스터마이징할 수 있다.
- 값을 지정해 줄 수 있다는 말같다. 예) self.name = 범
- 메서드 호출도 가능하다.
- 체인의 모든 convenience init은 인스턴스를 customize하고 self와 함께 작업이 가능하다.
- 처음에 올라가면서 "이거의 타입은 어떤거니?, 이만큼의 메모리를 차지하겠네" 이러면서 올라가고
- 내려올때, "이 프로퍼티는 이거, 이 프로퍼티는 저거" 이러면서 내려오다고 생각하자.
Initializer Inheritance and Overriding
- objc와 다르게 swift는 하위 클래스가 super 클래스의 이니셜라이저를 상속받지 않는다.
- super 클래스의 단순한 init이 super 클래스보다 상세해진 (내용이 많아진) 하위 클래스에 상속되어 올바르게 초기화되지 않은 하위 클래스의 인스턴스를 생성하는 데 사용되는 상황을 방지한다.
super 클래스의 이니셜라이저는 특정 상황에서 상속되지만, 안전하고 적절한 경우에만 상속된다. Automatic Initializer Inheritance 참조
- overriding으로 super class의 init과 동일한 매개변수를 사용하여 작성이 가능하다.
- 하위 클래스의 이니셜라이저 구현이 convenience init이여도 super class의 desiganted init을 재정의 하는 경우에도 overriding 키워드를 사용해야한다.
- super 클래스의 convenience init과 일치하는 하위 클래스의 init을 작성하면 , 규칙에 따라서 super class의 convenience init는 하위 클래스에서 직접 호출하는 것이 불가능하다.
- → 규칙2. convenience init은 같은 class에서 다른 init을 호출해야 한다.
- 따라서 super 클래스의 convenience init과 일치하는 구현을 할 때 오버라이드 수정자를 작성하지 않는다.
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\\(numberOfWheels) wheel(s)"
}
}
let vehicle = Vehicle()
print("Vehicle: \\(vehicle.description)")
// Vehicle: 0 wheel(s)
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
let bicycle = Bicycle()
print("Bicycle: \\(bicycle.description)")
// Bicycle: 2 wheel(s)
- super class에 인자가 0으로 지정된 sync 이니셜라이저가 있는 경우 하위 클래스의 모든 저장 프로퍼티에 값을 할당하고 super.init()을 생략 가능하다.
- super class의 init이 async 경우, await super.init()을 작성해야 한다.
- vehicle을 상속받는 다른 class
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() 의 위치
}
override var description: String {
return "\\(super.description) in a beautiful \\(color)"
}
}
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \\(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver
하위 클래스는 초기화 중에 상속된 변수 프로퍼티를 수정할 수 있지만, 상속된 상수 프로퍼티는 수정이 불가능하다.
Automatic Initializer Inheritance
- 기본적으로 하위 클래스는 상위 클래스의 이니셜라이저를 상속받지 않는다.
- 그러나 특정 조건이 충족되면 상위 클래스의 이니셜라이저가 자동으로 상속된다.
- 특정 조건이 무엇일까? 밑의 규칙들이 나와 있다.
- 일반적으로 이니셜라이저 오버라이드를 작성할 필요가 없다, 안전할 때마다 최소한의 노력으로 상위클래스의 이니셜라이저를 상속 할 수 있다.
- 하위 클래스의 도입하는 모든 새 프로 퍼티에 기본값을 제공한다고 가정하면 2가지 규칙이 적용된다.
규칙 1
하위 클래스가 designated init을 정의하지 않은 경우 상위클래스의 designated init을 모두 자동으로 상속받는다.
규칙 2
하위 클래스가 규칙 1에 따라 상속받거나 정의의 일부를 custom 구현을 제공하는 경우, 상위 클래스의 convenience init을 모두 자동으로 상속한다.
이 규칙들은 하위 클래스가 convenience init을 추가하는 경우에도 적용이 된다
- 하위 클래스는 규칙 2를 만족시키기 위한 일환으로 상위 클래스 designated init을 하위 클래스의 convenince init으로 구현이 가능하다.
- 예시)
class Food {
var name: String
var cost: Int
init(name: String, cost: Int) {
self.name = name
self.cost = cost
}
convenience init(name: String) {
self.init(name: name, cost: 1000)
}
}
class Ramen: Food {
var quntity = 450
}
let sin = Ramen(name: "신라면", cost: 1200)
let nuguri = Ramen(name: "너구리")
print("name: \\(sin.name) cost: \\(sin.cost) quntity: \\(sin.quntity)")
print("name: \\(nuguri.name) cost: \\(nuguri.cost) quntity: \\(nuguri.quntity)")
Designated and Convenience Initializers in Action
- designated init, convenience init, automatic init 상속이 작동하는 모습
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
- convenience init이 내부의 designated init을 호출하여 사용한다
- Food의 super class가 없으므로 init(name: String)는 초기화를 완료하기 위해 super.init()을 호출할 필요가 없다.
- convenience init을 통해 name의 값이 [Unnamed]인 기본 값을 가지는 init을 제공한다.
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
- Food에게 상속을 받는 RecipeIngredient를 정의 한다.
- 상속을 받기 때문에 designated init에서는 super클래스의 init을 호출해야 한다.
- convenience init은 같은 클래스의 init을 호출하기 때문에 self.init이 호출된다.
- init(name: String, quantity: Int)를 사용하여 새로운 변수인 quantity를 초기화를 진행하고
- Food의 init(name: String)에게 위임한다.
→ Two-Phase init의 safety check 1 을 충족한다. - convenience init(name: String)을 통해 quantity를 1로 정하는 init을 생성해줬다.
- name을 인자 값으로 받아와서 RecipeIngredient의 designated init에 위임한다.
- Food의 모든 designated init을 상속받고 그렇기 때문에 Food의 convenience init 또한 상속을 받는다.
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\\(quantity) x \\(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
- purchased는 항상 구매하지 않은 상태로 시작을 하기 때문에 init으로 생성할 때 값을 받아오는 것이 아닌 class의 기본 값을 false로 설정을 해놓는다.
- 모든 프로퍼티에 기본값을 제공하고 init을 정의하지 않기 때문에 super class로부터 모든 init을 자동으로 상속받는다.
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 6),
]
print(breakfastList[0].name) // [Unnamed]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘
- ShoppingListItem은 상속 받은 모든 init을 사용이 가능하다.
Failable intializers ( 실패 가능한 이니셜라이저)
- 초기화를 실패할 수 있는 클래스, 구조체, enum을 정의하는 것이 유용할 때가 있다.
- 왜일까?? → 하단의 동물 이름 예시 코드를 보면 납득이 간다
- 이러한 실패는 잘못된 초기화 매개변수 값, 필요한 외부 리소스가 없거나 초기화가 성공하지 못하게 하는 다른 조건으로 발생 가능하다.
- 실패가 가능한 초기화는 init?으로 표시한다.
동일한 매개변수 유형과 이름으로 init?과 init을 정의 할 수 없다.
- 실패 가능한 이니셜라이저는 특정한 값을 반환하지 않는다. 실패하면 nil을 성공한 경우는 값을 반환하지 않는다.
정확히는 이니셜라이저는 값을 반환하지 않는다. 초기화가 끝날 때까지 self가 올바르게 초기화가 되었는지 확인하는 역할이다. 내부에 return nil을 작성하더라도 초기화가 성공했을 때 반환 키워드로는 사용하지 않는다.
let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\\(wholeNumber) conversion to Int maintains value of \\(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"
let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int
if valueChanged == nil {
print("\\(pi) conversion to Int doesn't maintain value")
}
// Prints "3.14159 conversion to Int doesn't maintain value"
- 12345.0은 소수점 뒤가 0이기 때문에 변환이 성공
- pi는 소수점 뒤가 0이 아니기 때문에 실패한다.
동물 이름 예시)
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
- 이름에 빈값이 들어오면 nil을 반환
let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal
if let giraffe = someCreature {
print("An animal was initialized with a species of \\(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"
let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal
if anonymousCreature == nil {
print("The anonymous creature couldn't be initialized")
}
// Prints "The anonymous creature couldn't be initialized"
위의 ""은 non - optional값이다.
Failable Initializers for Enumerations
- enum에서 init?은 하나 이상의 매개 변수를 사용하여 enum case를 선택할 수 있다.
enum TemperatureUnit {
case kelvin, celsius, fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .kelvin
case "C":
self = .celsius
case "F":
self = .fahrenheit
default:
return nil
}
}
}
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("성공")
}
// Prints "성공"
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("실패.")
}
// Prints "실패"
- unknownUnit은 K, F, C를 제외한 값을 init할때 넣었기 때문에 실패한다.
Failable Initializers for Enumerations with Raw Values
- init을 진행할 때 rawValue를 받아와 일치하지 않으면 nil을 반환한다.
enum TemperatureUnit: Character {
case kelvin = "K", celsius = "C", fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."
let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
print("This isn't a defined temperature unit, so initialization failed.")
}
// Prints "This isn't a defined temperature unit, so initialization failed."
Propagation of Initialization Failure
- 다른 init과 마찬가지로 init?도 다른 init?으로 위임가능하다.
- 하위 클래스의 init?은 상위 클래스의 init?까지 위임이 가능하다.
- 어느 경우든 실패하면 초기화를 더 이상 진행하지 않는다.
실패할 수 있는 이니셜라이저를 실패할 수 없는 이니셜라이저에 위임할 수 있다. 잠재적인 실패 상태를 추가해야 하는 경우 이 방법을 사용한다.
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 }
self.quantity = quantity
super.init(name: name)
}
}
- CarItem에서 product init?으로 위임을 하고 있다.
- 호출 할때는 super.init?을 안하고 super.init으로 작성을 한다.
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \\(twoSocks.name), quantity: \\(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \\(zeroShirts.name), quantity: \\(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \\(oneUnnamed.name), quantity: \\(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"
- 정상적으로 초기화가 이루어지는 경우
- quantity가 1보다 작아 CarItem의 init? 조건을 만족하지 못하는 경우
- name의 값이 “”라서 product의 init?을 만족하지 못하는 경우
Overriding a Failable Initializer
- 다른 이니셜라이저와 마찬가지로 init?도 오버라이딩이 가능하다.
- 상위 클래스의 실패 가능한 이니셜라이저를 하위 클래스의 실패 불가능한 이니셜라이저로 오버라이딩도 가능하다.
- 상위 클래스의 초기화는 실패하지만 하위 클래스의 초기화는 실패할 수 없게 하는 것이 가능하다.
상위 클래스의 init?은 하위 클래스의 init으로 오버라이딩하는 경우, 상위 클래스 이니셜라이저까지 위임하는 유일한 방법은 상위 클래스의 init?의 결과를 강제 언래핑 하는 것이다.
init? → init으로 오버라이딩 가능
init → init?으로 오버라이딩 불가능
class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// this initializer creates a document with a nonempty name value
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class AutomaticallyNamedDocument: 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: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
- super class의 init?을 강제 언래핑으로 호출해 하위 클래스의 override init에 사용
- 상위 클래스의 init?을 사용하려면 강제 언래핑을 사용하여 호출해야된다.
상위 클래스의 init(name:)에 빈 문자열이 들어가 호출이 된다면, 강제 언래핑으로 인해 런타임 오류가 발생한다.
init! Failable Initializer
- init! → 옵셔널이랑 똑같이 생각하면 된다.
- init? → init! 으로 위임하거나 반대로 위임이 가능, override도 가능하다.
Required Initializers
- 클래스의 모든 하위 클래스는 required init을 구현해야 한다.
- 그렇기 때문에 UIkit에서 클래스를 상속받아서 구현하는 경우 init을 생성하면 required init이 없다는 메시지가 뜬다.
class SomeClass {
required init() {
// initializer implementation goes here
}
}
- init 앞에 required를 붙인다.
class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}
- required init 를 오버라이딩 하는 경우에는 override 키워드를 작성하지 않는다.
상속된 이니셜라이저로 요구 사항을 충족하는 경우에는 required init을 명시적으로 구현 안해도 된다.
Setting a Default Property Value with a Closure or Function
- 저장 프로퍼티의 기본값에 custom값 또는 설정이 필요한 경우, 클로저 또는 전역 함수를 사용하여 해당 프로퍼티에 대한 기본값을 제공할 수 있다.
- 프로퍼티가 속한 타입의 새 인스턴스가 초기화될 때 클로저 또는 함수가 호출되고 반환값이 기본값에 들어간다.
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
- 클로저의 끝에 항상 ()를 사용해야 실행하도록 지시를 한다.
- ()를 생략하면 클로저 자체를 할당하는 것
클로저가 실행되는 시점에 나머지 인스턴스는 초기화를 못한 상태이기 때문에 다른 프로퍼티에 접근이 불가능하다. 기본값이 있어도 마찬가지다.
예시) 체스판
struct Chessboard {
let boardColors: [Bool] = {
var temporaryBoard: [Bool] = []
var isBlack = false
for i in 1...8 {
for j in 1...8 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAt(row: Int, column: Int) -> Bool {
return boardColors[(row * 8) + column]
}
}
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"
- 체스판이 만들어 생성이 될때 클로저내부의 값을 반환하여 프로퍼티에게 준다.
Deinitializer
- 이니셜라이저와 반대의 역할을 한다.
- 메모리에서 해제되기 직전에 클래스의 인스턴스와 관련하여 작업을 수행할 수 있다.
- Deinitializer는 클래스의 인스턴스에만 구현이 가능하다. (핑크책)
- 인스턴스가 메모리에서 소멸하기 직전에 인스턴스의 데이터에 저장되어 있는내용을 파일에 저장해야 하는 경우 유용하다.
- deinit은 1개만 구현이 가능하다.
- 매개변수와 ()을 작성하지 않는다.
deint {
// 소멸하기전에 할 행동
}
'swift' 카테고리의 다른 글
WWDC 16 Understanding Swift Performance ( 2 / 2 ) (0) | 2024.06.21 |
---|---|
WWDC 16 Understanding Swift Performance (1/2) (0) | 2024.06.19 |
Closure 클로저 (0) | 2024.05.30 |