https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
목차 (눌러서 이동)
Value Types 를 위한 Initializer Delegation
Swift 의 Value type 인 Structures and Enumerations 을 위한 대리초기자에 대한 설명이다. (Class type 과 동작 방식이 다르기 때문에 구분)
Value types 은 상속을 지원하지 않아 상대적으로 간단하다. 해당 Value type 스스로가 제공하는 또 다른 초기자에게만 위임할 수 있다. 사용 방법은 Custom Initializer 에서 self.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)
}
}
첫 번째 초기자 init( ) 은 Default Initializer 로 이를 호출할 시 Properties 가 기본값으로 초기화된다. 두 번째 init(origin: Point, size: Size) 는 기능적으로 Memberwise Initializer 와 같다.
Memberwise Initializer 는 구조체를 위한 기본 초기자로 예를 들어, 위의 Size 구조체는 초기자가 없지만 아래처럼 초기화할 수 있다.
let twoByTwo = Size(width: 2.0, height: 2.0)
세 번째 init(center: Point, size: Size) 이 대리 초기자를 부르는 초기자인데, 인자로 받은 값으로 원점을 계산한 뒤 두 번째 초기자에게 값을 넘기며 초기화를 대리하도록 한다. 세 번째 초기자 자체에서 Properties 를 초기화할 수도 있지만, 이미 있는 기능을 활용하지 않을 이유는 없다.
Class types 를 위한 Initializer Delegation
Class types 에서 상속받는 모든 Stored Properties 는 초기자의 실행이 끝마치기 전에 초기화되어야 한다. Swift 는 이를 보장하기 위해 Designated Initializer 와 Convenience Initializer 를 정의해 놓았다.
Designated Initializer
Designated Initializer 는 Class 의 주요 초기자이다. 즉 여기에서 모든 Properties 를 초기화하고 적절히 상위 Class 의 초기자를 호출하여 상속 관계의 모든 초기화 작업을 진행하게 한다.
모든 Class 는 적어도 하나의 Designated Initializer 를 가져야 하고 이 조건은 초기자를 상속받는 방식으로도 만족시킬 수 있다.
init(parameters) {
statements
}
Convenience Initializer
Convenience Initializer 는 초기자를 보조하는 역할을 한다. Convenience Initializer 를 만들어 Designat ed Initializer 를 호출할 수 있다. 보조 초기자이므로 필요할 때만 작성한다.
convenience init(parameters) {
statements
}
Initializer Delegation Rules
Designated 와 Convenience 초기자의 관계를 단순화 하기 위해 Swift 는 다음과 같은 규칙을 정해두었다.
1. Designated 초기자는 상위 Class 의 Designated 초기자를 호출해야 한다.
2. Convenience 초기자는 같은 Class 의 다른 Convenience 초기자를 호출해야 한다.
3. Convenience 초기자는 궁극적으로 Designated 초기자를 호출해야 한다.
Subclass 의 Designated 초기자는 모두 Superclass 의 Designated 초기자를 호출하고 (Rule 1), Convenience 초기자는 자신의 Class 내의 다른 초기자를 호출하며 (Rule 2), 두 Class 모두 Convenience 초기자가 궁극적으로 Designated 초기자를 호출하고 있다. (Rule 3)
간단히 말해서, Designated 초기자는 항상 Delegate Up (상위 Class 에게 초기화 과정을 위임한다.) 하고, Convenience 초기자는 항상 Delegate Across(현재 Class 의 다른 초기자에게 과정을 위임한다.) 한다.
Two-Phase Initialization
Swift 의 Class 초기화 과정은 두 단계로 나뉜다. 첫 단계에서 모든 Stored Property 는 해당 Class 에서 제공하는 초기값이 할당된다. 두 번째 단계에서 Stored Properties 는 또 다른 값으로 할당될 기회가 주어진다.
두 단계의 초기화는 안전과 유연성을 동시에 제공한다. 값이 초기화 되기 전에 접근이 일어나는 것을 막고, 다른 초기자에 의해 예기치 않게 다른 값이 할당되는 것을 막는다. Swift Compiler 는 두 단계의 초기화를 안전하게 완료하기 위해 4 가지 사항을 확인한다.
1. Designated Initializer 는 상위 Class 의 초기자를 호출하기 전 자신의 모든 Properties 를 초기화해야 한다. (객체 Memory 는 Properties 의 초기 상태가 모두 결정되어야 준비된 상태로 취급된다.)
2. Designated Initializer 는 반드시 상위 Class 의 초기자를 호출한 후에 상속받은 Properties 를 초기화해야 한다. 그렇지 않으면 하위 초기자가 할당한 값이 상위 초기자에 의해 덮어씌어질 수 있다.
3. Convenience Initializer 는 반드시 다른 초기자를 호출한 뒤 Property 값을 초기화할 수 있다. 그렇지 않으면 Convenience Initializer 가 초기화한 값이 다른 초기자에 의해 덮어씌어질 수 있다.
4. Initializer 는 초기화의 첫 단계 (모든 Stored Properties 가 초기값이 결정됨) 가 끝나기 전에 Instance methods 를 호출하거나, Instance properties 의 값을 읽거나, self 의 값을 참조할 수 없다.
위 4 가지 사항을 염두에 두고 두 단계의 초기화 과정을 자세히 살펴보자.
1 단계
1. Designated 혹은 Convenience Initializer 가 호출되고 객체를 위한 Memory 가 할당된다. (할당은 되지만 아직 사용할 수 없다.)
2. Designated Initializer 는 해당 Class 의 모든 Stored Properties 를 모두 초기화 시킨다. Convenience 가 먼저 호출된다면 Properties 를 초기화하지 않고 다른 초기자를 먼저 호출한다. (이 Stored Properties 는 Memory 는 이제초기화가 완료되고 사용될 수 있다.)
3. Designated Initializer 는 상위 Class 의 초기자를 호출해 그들 자신의 Stored Properties 를 초기화 하도록 한다. (최상위 상속 Class 까지 도달.)
4. 최상위 Class 까지 모든 Properties 의 초기화를 완료하면 객체 Memory 는 완전히 초기화된 것이고 1 단계가 끝난다.
2 단계
1. 상위 Class 부터 내려오며 Designated Initializer 들은 객체를 추가로수정할 기회를 가지고 여기서는 self 키워드(객체 Memory 가 1 단계에서 초기화 완료되었으므로) 를 통해 Properties 를 수정하거나 Instance methods 를 호출할 수 있다.
2. 마지막으로 가장 먼저 호출되었던 Convenience Initializer 가 객체를 수정할 기회를 가진다. (역시 self 키워드를 사용할 수 있다.)
Initializer Inheritance and Overriding
기본적으로 Swift 의 하위 Class 는 상위 Class 의 초기자를 상속받지 않는다. (간소하게 구현된 상위 Class 의 초기자가 복잡한 하위 Class 를 불완전하게 초기화함을 막기 위한 것.)
상위 Class 의 Designated Initializer 를 상속받고 싶다면 override 키워드와 함께 똑같은 형태의 초기자를 작성하면 된다. (Designated 를 하위 Class 에서 Convenience 로 override 할 수도 있다.)
Convenience Initializer 는 하위 Class 에서 직접적으로 호출되지 않으므로 이를 상속할 때는 override 키워드를 사용하지 않는다.
하위 Class 에 Designated Initializer 가 없다면 자동으로 상위 Class 의 Designated Initializer 모두를 상속받는다.
하위 Class 가 상위 Class 의 Designated Initializer 를 모두 상속받고 있다면 자동으로 모든 Convenience Initializer 를 상속받는다.
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
RecipeIngredient 는 Food 의 모든 Designated Initializer 를 상속하고 있으므로 모든 Convenience Initializer 역시 상속받는다. 상속받은 초기자는 상위 Class 가 아닌 하위 Class 의 초기자를 호출한다. (모든 Designated 가 상속되어야 Convenience 를 상속하므로 하위 Class 에서도 역시 호출할 Designated Initializer 가 override 되어 구현되어 있다.)
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)
}
}
Failable Initializers
어떤 상황에서는 Class, Structure, Enumeration 의 초기화가 실패할 수도 있다. init? 키워드로 실패할 수도 있는 초기자를 선언할 수 있다. (일반적인 초기자와 같은 매개변수를 동시에 사용할 수는 없다.) 초기화가 실패해야 하는 지점에서 return nil 을 함으로써 실패했음을 알린다.
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
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
}
}
}
// Enums with raw values automatically receive a failable initializer.
enum TemperatureUnit: Character {
case kelvin = "K", celsius = "C", fahrenheit = "F"
}
Required Initializers
Initializer 선언에 required 키워드를 붙이면 모든 하위 Class 에서 해당 초기자를 반드시 구현해야 한다. 하위 Class 에서 required 초기자 구현 시 override 대신 required 키워드를 사용한다.
class SomeClass {
required init() { ... }
}
class SomeSubclass: SomeClass {
required init() { ... }
}
만약 상속받은 다른 Initializer 로 해당 Requirement 를 만족할 수 있다면 명시적으로 required init() 을 작성할 필요는 없다.
'iOS 개발 > Swift 문법' 카테고리의 다른 글
[iOS/Swift] KeyPath (0) | 2021.04.14 |
---|---|
[iOS/Swift] Optional type, guard let vs. if let (0) | 2021.04.12 |