Introduction
: Objective-C와 달리 Swift의 initializer는 값을 return 하지 않는다. 또한 Swift에는 initializer와 pair되는 deinitializer가 있다. deinitializer에서 instance가 dealloc 되기전에 해야되는 작업을 해야한다.
Setting Initial Values for Stored Properties
: Class와 Structure는 stored property들을 자신의 instance가 만들어 질때까지 적절한 값으로 세팅되어 있어야 한다.
즉, initializer 안에서 property의 초기값을 세팅하거나, property 선언과 동시에 default property 값을 세팅해야 한다.
* 위에 설명된 바와 같이 initializer안에서 property의 값을 초기화 하거나, property 선언과 동시에 default 값을 세팅하면 property observer들이 호출되지 않는다.
Initializers
init () {
// perform some initialization here
}
// 가장 심플한 형태의 initializer다.
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 Property Values
: property 초기화를 선언과 동시에 할 수도 있다.
struct Fahrenheit {
var temperature = 32.0
}
Customizing Initialization
Initialization Parameters
: 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
이처럼 원하는대로 initializer를 만들 수 있으며 각 initializer는 파라미터들을 가질 수 있다.
Parameter Names and Argument Labels
: initializer는 function과 같이 parameter 이름과 argument label을 가질 수 있다. parameter 이름은 initializer 내에서 paramenter를 참조할 때 쓰이고, argument label은 initializer를 호출할 때 쓰인다.
그러나 initializer는 함수와 달리 자체적으로 이름을 가질 수 없다. 그래서 initializer끼리의 구별은 parameter의 이름과 타입으로 initializer를 구별한다. 그래서 Swift는 자동으로 Initializer의 parameter 이름을 argument 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
}
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
initializer를 호출할 때는 꼭 argument label을 명시해줘야 한다. 그렇지 않으면, compile error가 발생한다.
let veryGreen = Color(0.0, 1.0, 0.0)
// compile error 발생~
Initializer Parameters Without Argument Labels
: initializer의 argument label을 생략할 수 있는 방법이 있다. argument label이 들어갈 자리에 '_'를 명시하면 된다.
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: Double) {
temperatureInCelsius = celsius
}
}
let bodyTemperature = celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0
Optional Property Types
: 만약 당신이 만든 custom type이 optional type의 stored property를 갖고 있다면, 이런 의도일 것이다. "이 property는 initialization 동안에는 값을 가질수 없는 property 야" 또는 "이 property는 값을 가지고 있다가도 나중에는 값을 가질 수 없는 상태도 존재할거야"
optional property는 nil 값으로 초기화 된다. 이것은 아직 초기화 되지 않은 상태라는것을 명시적으로 표현하기 위함이다.
class SurveyQuestion {
var text: String
var response: String?
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?"
cheeseQuestion.response = "Yes, I do like cheese."
cheeseQuestion.response는 question이 초기화 되기전까지는 값을 가질 수 없는 상태이다. 위의 예제는 억지로 만들어낸 상황이라고 생각할 수 있지만, 코딩을 하다보면 위와 같은 상황은 언제든지 겪을 수 있다.
Assigning Constant Properties During Initialization
: initialization 과정중에 constant property에도 값을 할당하는 것이 가능하다. 만약 클래스의 경우라면 마찬가지로 initialization 과정중에 constant property에 값 할당이 가능하지만, subclass에서는 불가능하다.
위 SurveyQuestion의 text property를 let으로 변경하는것이 더 바람직해보인다. 한번 해보자.
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.response = "I also like beets. (But not with cheese.)"
Default Initializers
: Class나 Structure는 Default initializer를 가질 수 있다. 단, 조건이 있다. 모든 property들이 default 값을 가지고 있어야 한다. 그리고 다른 initializer를 제공하지 않아야 한다.
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
위 클래스의 name property는 optional이기 때문에 default nil값을 가진다. 그러므로 모든 property들이 default값을 가지게 되므로 default initializer를 통해 instance를 만들 수 있다.
Memberwise Initializers for Structure Types
: Structure는 다른 custom initializer를 제공하지 않으면 Memberwise Initializer로 instance를 생성할 수 있다. 모든 property를 초기화 하는 initializer이고 property 이름을 argument label로 사용한다.
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
* 만약 당신이 만든 custom value type이 default initializer, memberwise initializer, custom initializer를 동시에 갖기를 원한다면, custom initializer를 extension에 선언하면 된다.
Initializer Delegation for Value Types
: initializer는 다른 initializer를 호출할 수 있다. 이것은 instance 초기화의 한 부분으로 동작하며, 여러개의 initializer가 존재할 때, 중복 코드 생성을 피하기 위함이다.
delegation 동작은 value type(structure, enumeration)과 class type간에 차이가 존재한다. 그도 그럴것이 value type은 상속이 불가능하기 때문에 initializer delegation 동작이 비교적 심플하다. initializer delegation이 자기 자신 내에서만 이루어 지기 때문이다.
Class는 상속이 가능하기 때문에 Class는 슈퍼클래스의 property들까지 initialization 동안에 적절한 값으로 초기화 해줘야 하는 책임을 가지고 있다.
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 () {}
// default initializer랑 동작이 똑같지만 밑에 custom initializer와 같이써야 하기 때문에 custom initializer인것 처럼 보이기 위한 fake이다.
init (origin: Point, size: Size) {
self.origin = origin
self.size = size
}
// memberwise initializer랑 동작이 똑같지만 밑에 custom initializer와 같이써야 하기 때문에 custom initializer인것 처럼 보이기 위한 fake이다.
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)
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)
Class Inheritance and Initialization
: 슈퍼클래스의 property들을 포함해서 클래스의 모든 property들은 initialization time에 적절한 값으로 초기화 되어야 한다.
스위프트는 클래스 타입에 대한 두 종류의 initializer를 제공한다. designated initializer와 convenience initializer가 그것이다.
Designated Initializers and Convenience Initializers
: Designated Initializer는 현재 클래스의 모든 property들을 초기화 하고, 슈퍼 클래스의 property들을 초기화 하기 위해서 적절한 슈퍼클래스의 initializer를 호출해 준다.
대부분의 클래스는 하나의 Designated Initializer를 가지고, 왠만하면 2개를 넘지 않는다. 그리고 모든 클래스는 반드시 적어도 하나의 designated Initializer를 가지고 있어야 한다.
: Convenience Initializer는 같은 클래스의 Designated Initializer를 호출해야 하는 책임을 가지고 있다. Convenience Initializer는 꼭 생성해야할 필요는 없다. 필요할때 생성해서 사용하면 된다.
Syntax for Designated and Convenience Initializers
: Designated Initializer는 일반적인 initializer 선언방법과 동일하게 하면 된다.
init (parameters) {
//statements
}
convenience initializer는 'convenience' 키워드가 앞에 붙는다.
convenience init (parameters) {
//statements
}
Initializer Delegation for Class Types
: 스위프트는 initializer delegation에 대해 3가지 rule을 정의했다.
Rule 1
- designated initializer는 반드시 슈퍼클래스의 designated initializer를 호출해야 한다.
Rule 2
- convenience initializer를 반드시 같은 클래스의 다른 initializer를 호출해야 한다.
Rule 3
- convenience initializer는 결국 designated initializer를 호출하도록 구성되어야 한다.(즉, convenience initializer끼리 메세지 체인이 일어나도 결국엔 마지막 convenience initializer는 designated initializer를 호출해야 한다.)
Two-Phase Initialization
: 스위프트는 Class initialization을 하는데 두 단계 과정을 거치도록 강제한다.
첫번째는, 모든 stored property들이 적절한 값으로 초기화 되어야 한다.
두번째는, 각 클래스들이 stored property들을 customize할 수 있는 기회를 갖는다.
위 두단계는 순차적으로 이루어 져야하며, 두 번째 단계를 생략해도 된다.
* 위 두 단계의 초기화 과정은 Objective-C의 초기화 과정과 비슷하지만, 가장 큰 차이는 첫번째 단계인 stored property를 초기화 하는 과정에 있다. Objective-C는 default로 모든 stored property들을 0 또는 nil로 초기화 시키지만, 스위프트는 개발자가 원하는 값으로 초기화 시킬 수 있다.
Safety check
: 스위프트의 compiler는 위 두 단계의 initialization 과정이 올바르게 이루어졌는지 체크하기 위해 4가지 Safety check를 한다.
Safety check1
- designated initializer가 슈퍼클래스의 designated initializer를 호출하기 전에 자신 클래스의 모든 stored property들이 적절한 값으로 초기화 되었는지 체크한다.
Safety check2
- designated initializer가 상속받은 property들의 값을 customize 하기전에 슈퍼클래스의 designated initializer를 호출해서 상속받은 property들을 초기화 하는지 체크한다.
만약 슈퍼클래스의 designated initializer가 불리기 전에 상속받은 property들을 customizing한다면, 슈퍼클래스의 designated initializer에서 해당값들이 overwritten될 수 있다.
Safety check3
- convenience initializer는 property(슈퍼 클래스의 property 포함)들에게 값을 지정하기 전에 다른 initializer를 미리 호출해야 한다.
만약 그렇지 않으면, 초기화한 값이 다른 initializer에 의해 overwritten될 수 있다. 즉 convenience initializer는 property를 초기화 하는것이 아니라 property를 customizing하는 곳이다. 그러기 위해서는 미리 property들의 값을 초기화하는 작업이 선행되어야 한다.
Safety check4
- Phase1(모든 property들이 적절한 값으로 초기화 되는것)이 끝나기 전에 self를 참조할 수 없고, property를 참조하고 있는 instance method들을 호출할 수 없다.
그 이유는, 모든 property들이 적절한 값으로 초기화 되기 전까지 클래스의 instance는 준비된 상태가 아닌 불완전한 상태라고 판단하기 때문이다.
Two-Phase Initialization In Detail
: 위 Safety check를 토대로 Two-Phase Initialization과정을 좀 더 상세히 들여다 보도록 하겠다.
Phase1
- designated 또는 convenience initializer가 호출된다.
- 클래스 인스턴스에 대한 메모리가 할당된다. 그렇지만 아직 초기화가 완료된 상태는 아니다.
- designated initializer를 통해 자신 클래스의 property에 대한 초기화를 진행한다.
- 슈퍼클래스의 designated initializer를 호출한다. 슈퍼클래스의 designated initializer 역시 자신의 property에 대한 초기화를 진행하고 다시 슈퍼클래스의 designated initializer를 호출해서 root 클래스까지 이 과정이 반복된다.
- root 클래스까지 initialization 과정이 완료되면 모든 property들의 초기화가 완료되었다는 것이고, 이는 클래스 인스턴스에 할당된 모든 메모리가 적절한 값으로 초기화 되었다는 것이다. 즉, Phase 1이 완료된다.
Phase2
- root 클래스에서 bottom 클래스까지 역으로 initialization 과정이 delegate down되면서 각 designated initializer는 필요하다면 property들에 원하는 값을 대입 시키거나 self 또는 다른 instance method 호출을 통해서 instance customizing 한다.
- 마지막으로 convenience initializer에서 instance를 customizing할 수 있는 기회는 갖는다.
Initializer Inheritance and Overriding
: 스위프트는 Objective-C와 다르게 슈퍼클래스의 initializer를 default로 상속받지 않는다. 그 이유는 스위프트는 클래스가 property의 default 초기값을 제공하지 않기 때문이다. 그래서 instance를 생성할 때 자동상속된 슈퍼클래스의 initializer를 호출한다면 자신 클래스의 property들을 초기화가 되지 않을 위험이 있다.
만약 슈퍼클래스의 designated initializer를 override 하기를 원한다면, 앞에 @override 키워드를 붙여줘야 한다. default initializer를 override할때도 마찬가지다.
@override 키워드를 붙이면 스위프트 컴파일러는 슈퍼클래스의 initializer와 매칭되는지 체크를 한다.
슈퍼클래스의 convenience initializer와 매칭되는 initializer를 구현할때에는 앞에 @override 키워드를 붙이지 않는다.
Automatic Initializer Inheritance
: 자식클래스는 슈퍼클래스의 initializer를 자동으로 상속받지 않지만, 특별한 경우에 자동으로 상속받는다.
Rule 1
- 자식클래스가 새로운 designated initializer를 제공하지 않았다면, 슈퍼클래스의 모든 designated initializer를 상속받는다.
Rule 2
- 만약 자식클래스가 슈퍼클래스의 모든 designated initializer의 implementation을 제공 한다면(자동으로 상속받았거나, 실제로 override해서 custom implementation을 제공한 경우 둘다 해당된다. 또한 슈퍼클래스의 designated initializer를 자식클래스에서 convenience initializer로 구현한 경우도 해당된다. 단, 슈퍼클래스의 여러개의 designated initializer 중 자식클래스에서 일부만 override한 경우는 해당되지 않는다.) 슈퍼클래스의 모든 convenience initializer도 상속받는다.
위 두가지 rule은 자식클래스가 custom convenience initializer를 가지고 있어도 해당된다.
Designated and Convenience Initializers in Action
: 실제 Designated, Convenience Initializer들이 어떻게 동작하는지 알아보도록 하자.
class Food {
var name: String
init (name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
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)
}
}
RecipeIngredient 클래스는 자신의 stored property를 초기화 시키는 designated initializer 가지고 있고, 부모클래스의 designated initializer를 override 해서 구현하고 있기 때문에 부모클래스의 convenience initializer를 자동으로 상속 받았다.
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 ? "v" : "x"
return output
}
}
ShoppingListItem은 유일한 stored property인 purchased에 선언과 동시에 초기값을 제공하고 있기 때문에 이것을 초기화 해주는 designated initializer가 필요하지 않다.
그리고 부모클래스의 어떤 designated initializer도 override하고 있지 않고, 자신의 custom designated initializer도 구현하지 않고 있기 때문에 부모클래스의 모든 designated initializer를 자동으로 상속 받는다. 또한 부모클래스의 모든 designated initializer를 구현하고 있기 때문에(자동 상속으로..) 부모 클래스의 모든 convenience initializer도 자동 상속받는다.
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x Orange juice v
// 1 x Bacon x
// 6 x Eggs x