목차
생성자란?
initialization 이란 클래스, 구조체, 열거형 인스턴스의 생성을 준비하는 과정을 의미합니다
이 과정에는
•
stored property 의 초기값 설정.
•
새 인스턴스가 생성되는데 필요한 작업들
이 포함됩니다.
objective c 와는 다르게 Swift 는 값을 return 하지 않습니다.
주된 역할은 타입의 새 인스턴스를 사용 전 올바르게 초기화 하는 데에 있습니다.
클래스의 인스턴스는 또한 deinitializer 도 존재합니다.
Setting Initial Values For Stored Propeties
클래스와 구조체의 인스턴스는 생성되기 전
반드시 모든 stored property 가 올바른 값으로 설정되어야만 합니다.
1.
initializer 내부에서 stored property 의 초기값을 설정
2.
프로퍼티의 선언부에서 초기값을 할당
Initialzers
Initializer(생성자) 는 특정 타입의 인스턴스를 생성할때 호출합니다.
가장 간단한 형태는 어떠한 파라미터도 받지 않는 형태로 아래와 같습니다
init() {
// perform some initialization here
}
init(temparautre: Double) {
}
Swift
복사
아래의 예시는 새 구조체 Fahrenheit 를 정의해서 화씨로 표현된 온도를 저장하는 예시입니다. Fahrenheit 구조체는 하나의 저장 프로퍼티 temperature 를 가지며, 이의 타입은 Double 입니다.
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"
Swift
복사
위 예씨에서 생성자는 어떠한 파라미터도 받지 않습니다.
생성자 내부에서 저장 프로퍼티 temperature 의 값은 32.0 으로 저장됩니다.
Default Property Values
위 예시에서는 생성자 내부에서 저장 프로퍼티의 초기값을 설정해 주었습니다.
이번에는 프로펕의 선언부에서 프로퍼티의 초기값을 설정하는 방법에 대해서 알아보겠습니다. 프로퍼티가 정의 된 곳에 초기값을 할당하기만 하면 됩니다.
프로퍼티가 항상 같은 초기값을 가진다면 생성자에서 초기값을 설정해 주는 대신, 초기값을 선언부에서 할당하세요. 결과는 똑같겠지만, 코드의 길이가 간결해집니다.
위 Fahrenheit 구조체를 더 간단한 방식으로 바꿔보겠습니다.
struct Fahrenheit {
var temperature = 32.0
}
Swift
복사
Customizing Initialization
Initialization Parameters
생성자의 정의에 파라미터를 사용할 수 있습니다.
파라미터들의 타입과 이름을 명확하게 정의해야하며, 이는 함수와 메서드의 파라미터 정의 구문과 동일합니다.
다음 예시에서는 Celsius 라고 불리는 구조체를 정의했습니다.
이 구조체는 섭씨로 표현된 온도 값을 저장합니다.
Celsius 구조체는 두개의 커스텀 생성자를 가지는데.
•
init(fromFahrenheit:)
•
init(fromKelvin:)
위와 같습니다. 두 생성자는 서로다른 온도 표현으로 부터 인스턴스를 생성합니다.
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
Swift
복사
Parameter Names and Argument Labels
함수와 메서드의 파라미터 처럼 생성자 내부에서 사용되는 파라미터도 생성자를 호출할 때 사용되는 argument label 을 가질 수 있다.
생성자는 이름이 없기 때문에, 파라미터의 이름과 타입으로 생성자를 구분하게 된다.
이러한 이유로 Swift는 개발자가 따로 argument lable 을 설정하지 않으면 모든 파라미터에 대해 자동으로 argument lable 을 제공한다.
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)
//이러한 argument lable 없이 생성자를 호출하면 오류가 발생한다.
let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required
Swift
복사
위처럼 argument lable 없이 생성자를 호출하면,
Swift 에서 자동으로 생성해준 argument lable 과 일치하지 않기 때문에 컴파일 에러가 발생한다.
Initializer Parameters Without Argument Lables
만약 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
Swift
복사
위 예시에서는 init(_ celsius: Double) 와 같이 argument label 을 사용하지 않는 생성자가 존재하기 때문에 컴파일 에러가 발생하지 않습니다.
Optional Property Types
저장 프로퍼티의 타입을 optional 로 지정할 수 있습니다
이렇게 지정된 optional 프로퍼티는 생성될 때 초기값을 가지지 않아도 됩니다
따라서 이는 다음의 경우에 활용이 가능합니다.
•
객체 생성시 할당이 불가능한 경우.
•
값을 객체 생성 이후로 미루고자 하는 경우
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."
Swift
복사
optional 로 선언된 저장프로퍼티는 nil 값이 자동으로 할당됩니다.
Assigning Constant Properties During Initialization
initialization 과정 중 언제든 상수 프로퍼티를 할당할 수 잇습니다. 단, initialization 이 끝나기 전까지 할당 해야 합니다. 한번 상수 프로퍼티가 할당되면 그 값은 변경될 수 없습니다.
클래스의 객체의 경우, 상수 프로퍼티는 initialization 도중에 오직 class 에 의해서만 변경이 가능합니다.
subclass 에 의해서는 변경 될 수 없습니다
위의 SurveyQuest 의 예시의 text 변수 프로퍼티를 상수 프로퍼티로 수정해 보겠습니다. text 프로퍼티는 이제 상수이지만 class 의 생성자 내부에서는 값의 할당이 가능해집니다.
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.)"
Swift
복사
Default Initiailzers - 기본 생성자
따로 initializer 를 선언하지 않으면 기본 생성자 사용 가능
•
모든 프로퍼티가 초기값으로 초기화됨.
다음 예시에서는 name, quantity purchase state 와 같은 정보들을 캡슐화 하는 ShoppingListItem 클래스를 정의합니다.
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
Swift
복사
위 클래스의 모든 프로퍼티들은 초기값이 존재하고, 어떠한 상위 클래스로 부터 상속도 받지 않았기 때문에 ShoppingListItem 클래스는 자동으로 기본 생성자의 사용이 가능해집니다.
Memberwise Initializers for Structure Types
structure 의 경우 생성자를 따로 정의하지 않으면, 파라미터로 초기값을 설정 가능한 memeberwise iniitializer 제공.
Memeberwiise Initializer
•
프로퍼티에 초기값이 없어도 생성가능.
•
초기값이 설정되어있는 프로퍼티에 한해서는 파라미터 생략 가능.
아래의 Size 라는 구조체는 두개의 프로퍼티를 가지며 두 프로퍼티 모두 Double 형으로 추론됩니다.
Size 구조체는 자동으로 init(width:height) 라는 memberwise initializer 를 갖습니다.
아래는 이를 이용해서 새 인스턴스를 생성하는 모습입니다.
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
Swift
복사
memberwise initializer 를 호출하면 초기값을 갖는 모든 프로퍼티들은 생략이 가능해집니다.
위 예시에서는 두 프로퍼티 모두 초기값을 가지므로 두 프로퍼티 모두 생략이 가능합니다.
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"
Swift
복사
이렇게 생략된 프로퍼티는 초기값으로 초기화됩니다.
Initializer Delegation for Value Types
생성자 내부에서 다른 생성자 호출 가능. = initializer delegation
•
중복 코드 피할 수 있다.
생성자는 초기화 과정 도중 또다른 생성자를 호출할 수 있습니다.
이 과정을 initializer delegation 이라고 부르며, 이 과정을 통해 여러 생성자의 중복 코드를 피할 수 있습니다.
initializer delegation 이 동작하는 방식과 어떤 형태의 delegation 이 허용되는지에 대한 규칙이 존재합니다.
이 규칙은 type 이 value type 인지 reference type 인지에 따라 달라집니다.
•
Value type 의 경우
상속을 지원하지 않기 때문에, initializer delegation 의 과정이 비교적 간단합니다.
상속하지 않기 때문에, 그들 자신의 생성자만 호출이 가능하기 때문이죠
Value 타입의 경우 self.init 을 사용해 동일한 value 타입의 생성자를 참조할 수 있습니다.
(단, 오직 생성자 내부에서만 self.init 을 호출할 수 있습니다.)
밸류 타입에 대해 커스텀 생성자를 정의하면 더이상 기본 생성자, memberwise 생성자를 이용할 수 없습니다.
•
만약 커스텀 생성자와 함께 기본생성자, memberwise 생성자를 같이 사용하고자 한다면, 커스텀 생성자를 밸류 타입의 extension 내부에 구현하세요.
이 제약 조건은 더 복잡한 이니셜라이저에서 제공되는 추가 필수 설정이 자동 이니셜라이저 중 하나를 사용하는 누군가에 의해 실수로 우회되는 상황을 방지합니다.
•
Class 의 경우
다른 클래스로 부터의 상속이 가능합니다.
따라서 생성과정에서 상속 받은 모든 저장 프로퍼티들에 대해 적절한 값이 할당 될것을 보장해야합니다.
아래의 예시에서는 직사각형을 나타내는 Rect 라는 커스텀 구조체를 정의하였습니다.
이 구조체는 두개의 보조 구조체인 Size Point 를 필요로 합니다.
각자의 프로퍼티들의 기본 값은 0.0 으로 동일합니다
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
Swift
복사
Rect 구조체는 3가지 방식으로 initialize 할 수 있습니다.
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 으로 대체
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
Swift
복사
아래는 각각에 대한 initialize 예제입니다.
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)
Swift
복사
정리
•
init() {} : 어떠한 생성자도 정의하지 않았을 때 제공되는 기본 생성자와 동일
•
기존에 존재하는 생성자와 중복되는 코드 >> self.init 으로 대체
Class Inheritance and Initialization
상위 클래스로 부터 상속받은 저장 프로퍼티를 포함한 모든 프로퍼티는 생성과정에서 반드시 초기값이 할당되어야 합니다.
Swift 에서는 두가지 종류의 생성자를 제공해 모든 저장 프로퍼티가 초기값을 갖도록 합니다.
•
Designated initializer - 지정 생성자
•
convenience initializer - 편의 생성자
Designated Initializer and Convenience Initializers
일반적으로 우리가 init() {} 으로 구현한 모든 생성자를 지정 생성자라고 부릅니다.
•
편의 생성자의 경우
클래스의 다른 생성자를 보조하는 역할이라고 생각하면 됩니다.
우리가 일반적으로 사용하는 init 은 전부 Designated Initializer(지정 생성자) 이며,
모든 클래스는 항상 하나 이상의 지정 생성자가 필요합니다.
어떤 경우에서는 , 상위클래스의 지정생성자를 하나 이상 상속받을 경우 위 조건이 충족되기도 합니다.
Convenience Initializer(편의 생성자) 는 부수적인 생성자 입니다.
편의 생성자는 클래스의 다른 생성자를 보조하는 역할을 하는데요
convenience init 안에서 Designated init 을 호출하여 초기화를 진행할 수 있습니다
class Person {
var name: String
var age: Int
var gender: String
init(name: String, age: Int, gender: String) {
self.name = name
self.age = age
self.gender = gender
}
convenience init(age: Int, gender: String) {
self.init(name: "zedd", age: age, gender: gender)
}
}
Swift
복사
이런 식으로 몇가지 파라미터를 생략하고
파라미터로 넘겨주지 않은 값은 임의로 지정한 생성자로 활용이 가능합니다.
해당 클래스의 특별 케이스나 입력 밸류 타입을 위한 클래스의 인스턴스를 생성하는 편의 생성자를 정의할 수도 있습니다.
Syntax for Designated and Convenience initializer
지정 생성자는 다른 생성자와 같은 방식으로 정의합니다
init(parameters) {
statements
}
Swift
복사
편의 생성자는 같은 방식으로 정의하나 init 키워드 앞에 convenience modifier 를 작성하면 됩니다.
convenience init(parameters) {
statements
}
Swift
복사
Initializer Delegation for Class types
지정 생성자와 편의 생성자 간의 관계를 단순화 하기 위해, Swift 는 아래의 3가지 규칙을 적용합니다.
Rule 1 : 지정 생성자는 반드시 바로 위 상위 클래스의 지정 생성자를 호출한다
Rule 2 : 편의 생성자는 같은 클래스의 다른 생성자를 호출해야만 한다
Rule 3 : 편의 생성자는 반드시 마지막에는 지정생성자를 호출해야만 한다.
위 3가지 규칙을 쉽게 말하면
•
지정 생성자는 항상 delegate up
•
편의 생성자는 항상 delegate across
위 그림은 상위 클래스가 하나의 지정 생성자와 두개의 편의 생성자를 갖는 예시입니다
하나의 편의 생성자는 다른 편의 생성자를 호출하고 이 호출된 편의 생성자는 하나의 지정생성자를 호출합니다.
위 그림은 rule 2 와 rule 3 를 만족합니다.
두 지정 생성자 모두 하나의 지정생성자만을 호출하므로 rule 1 또한 만족합니다.
이 규칙들은 사용자가 각각의 클래스들의 인스턴스를 생성하는 방식에는 영향을 미치지 않습니다. 모든 생성자는 생성자가 속한 클래스의 인스턴스를 완전히 생성하는데 사용됩니다. 규칙들은 오직 클래스의 생성자들을 구현하고자 하는 방식에만 적용됩니다.
아래 그림은 더 복잡한 위계관계를 가지는 네개의 클래스에 대한 예시입니다.
위 예시에서는 지정생성자가 클래스 생성과정에 있어서 funnel 기능을 어떻게 수행하는지를 나타냅니다.
Two Phase Initialization
Swift 의 클래스 생성은 두단계로 나누어 집니다.
•
1단계에서는 각각의 저장 프로퍼티가 초기값으로 할당됩니다.
한번 모든 저장 프로퍼티의 초기 상태가 결정되고 나면, 두번째 단계가 시작됩니다.
•
2단계에서는 새 인스턴스가 사용되기전에 클래스에게 자신의 프로퍼티들을 커스터마이징할 기회가 주어집니다.
왜 두단계로 나누는가?
생성과정 자체를 안전하게 만들기 위해
두단계 생성과정은 프로퍼티의 값 들이 초기화 되기 전에 접근 되는 것을 방지합니다.
또한 프로퍼티의 값들이 다른 생성자들에 의해 의도치 않게 다른 값으로 설정되는 것을 방지합니다.
Swift 의 두단계 생성과정은 Objective C 의 생성과정과 유사하다. 주된 차이점은 1다계에서 Objective C 는 모든 프로퍼티에 0 또는 null 값을 할당한다. Swift 의 생성과정은 더 유연하다. 커스텀한 초기값을 할당하도록 허용하고 0 또는 nil 값이 초기값으로 허용되지 않는 경우에도 적용이 가능하다.
Swift 컴파일러는 유용한 네가지 safety checks 기능을 수행합니다.
Safety Check 1
지정 생성자는 상위 클래스의 지정생성자를 delegate up 하기 전에 반드시 자신이 속한 클래스의 모든 프로퍼티를 초기화할 것을 보장해야한다.
위에서 언급했듯이, 객체의 메모리는 저장된 모든 프로퍼티의 초기 상태가 결정되었을 때 비로소 완전히 초기화되는 것으로 간주된다. 이 규칙이 충족되기 위해서는 지정생성자가 delegate up 하기 전에 모든 속성이 초기화되었는지 확인해야 합니다.
Safety Check 2
지정생성자는 상속받은 프로퍼티에 값을 할당하기 전에 상위 클래스의 생성자를 delegate up 해야합니다. 만약 그렇지 않으면 상위 클래스 자체의 생성과정 중에 해당 프로퍼티는 overwrite 즉 덮어 씌어질 것입니다.
Safety Check 3
편의 생성자는 모든 프로퍼티에 값을 할당하기 전에 다른 생성자를 delegate 해야합니다. 만약 그렇지 않다면 편의 생성자가 할당한 새로운 값이 클래스의 지정 생성자에 의해 덮어 씌어질 것입니다.
Safety Check 4
생성자는 다른 인스턴스 메서드를 호출해서는 안되고, 모든 인스턴스 프로퍼티의 값을 읽어와서는 안되며, 1단계 생성과정이 완수되기 전에 self 를 값으로서 참조해서는 안됩니다.
첫 단계가 끝나기 전까지 클래스의 인스턴스는 유효하지 않습니다.
1단계 생성과정이 끝나면 클래스의 인스턴스가 유효하며, 이때 프로퍼티들은 오직 접근 (read) 만 가능하며 메서드도 호출만 가능해집니다.
아래는 두단계 생성과정이 동작하는 방식입니다.
Phase1
•
지정 또는 편의 생성자가 호출된다
•
클래스의 새 인스턴스를 위한 메모리가 할당된다. 메모리는 아직 초기화 되지 않은 상태이다.
•
지정생성자는 자신이 속한 클래스의 모든 프로퍼티가 값을 갖는지 확인한다.
•
지정 생성자는 상위 클래스의 생성자를 호출해 자신의 저장프로퍼티에 같은 동작을 수행하도록 한다
•
연쇄반응이 끝날때까지 위 동작을 반복한다
•
연쇄반응이 끝나면 final 클래스는 자신의 모든 저장프로퍼티가 값을 가지는 지 확인하고 인스턴스의 메로기는 완전히 초기화 된것으로 고려되며 1단계가 종료된다.
Phase 2
•
연쇄 반응의 맨 꼭대기부터 다시 아래로 동작을 시작합니다. 각각의 지정 생성자는 인스턴스를 커스터 마이징할 수 있는 권한을 가집니다. 생성자는 이제 self 키워드에 접근 가능해지며, 자신의 프로퍼티를 변경할 수 있어지며, 인스턴스 메서드 또한 호출할 수 있게 됩니다.
•
마지막으로 모든 convenience initializer 들이 인스턴스를 커스터마이즈할 선택권을 가지며, self 를 사용할 수 있게 됩니다.
위 예시에서 initialization 과정은 하위 클래스의 convenience initializer 를 호출하면서 부터 시작합니다.
편의 생성자는 아직 어떠한 프로퍼티도 변경이 불가능합니다.
편의 생성자가 같은 클래스 내의 지정 생성자로 delegate across 합ㅈ니다.
지정 생성자는 safety cheak 1 에 따라, 하위 클래스의 모든 프로퍼티가 값을 가지도록 보장합니다.
그 다음 상위 클래스의 지정 생성자를 호출하여 연쇄 반응의 위로 initialize 를 계속합니다.
상위 클래스의 지정 생성자가 상위 클래스의 모든 프로퍼티가 값을 가지도록 보장합니다.
더이상 initialize 할 상위 클래스가 없기 때문에 더이상 delegation 이 이루어지지 않습니다.
상위 클래스의 모든 프로퍼티가 초기값을 가지자 마자, 상위 클래스의 메모리는 완전히 initialize 된 것으로 고려 되며 phase 1 이 종료됩니다.
여기서 부터 phase 2 의 작동 방식입니다.
상위 클래스의 지정 생성자는 이제 인스턴스를 커스터마이징할 기회를 갖습니다.
상위클래스의 지정생성자가 종료되면, 하위 클래스의 지정 생성자가 추가적인 커스터마이징을 수행합니다.
마지막으로 하위 클래스의 지정 생성자가 종료 되면 맨 처음 호출 되었던 편의 생성자 가 추가적인 커스터마이징을 수행합니다.
Initializer Inheritance and Overriding
생성자는 기본적으로 상속되지 않습니다.
생성자가 상속될 경우, 하위 클래스가 온전히 initialize 되기 전에 상위 클래스의 생성자가 인스턴스를 생성하는 상황이 발생할 수 있습니다.
상위 클래스의 생성자는 특정 상황에서는 상속 됩니다. 하지만 이는 안전할 때만 가능합니다. 더 자세한 정보는 아래를 참고하세요 Automatic Initializer Inheritance
하위 클래스에 상위 클래스와 동일한 생성자를 사용하고 싶다면, 하위 클래스 내부에 그 생성자들을 직접 구현해야합니다.
이는 지정 생성자를 override 하면 편합니다.
override 를 하위 클래스의 생성자 정의부 앞에 작성하면 됩니다.
자동으로 제공되는 기본 생성자를 override 할 때도 마찬가지입니다.
override 한 프로퍼티, 메서드, subscript 처럼, override 키워드는 Swift 로 하여금 상위 클래스에 일치하는 지정 생성자가 존재하는 지 확인하게 합니다. 그리고 override 하는 생성자에 대한 파라미터를 의도한 바와 같이 구체화 되도록 합니다.
상위 클래스의 convenience init override 불가.
반대로, 상위 클래스의 convenience init 과 대응되는 init 을 만들고자 한다면, 하위 클래스에서는 이 convenience init 을 호출할 수 없기 때문에, 이를 override 할 수 없습니다.
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)
Swift
복사
위 예시에서 Bicycle 의 init() 내부에서 super.init() 이 먼저 호출 됨으로써
numberOfWheels 변수가 초기화가 보장 된 후, 2로 변경될 기회를 얻습니다.
•
하위 클래스의 생성자가 초기화 과정에서 상위 클래스 프로퍼티에 대해 어떠한 커스터마이징도 하지 않고,
•
상위클래스에 인자가 없는 지정 생성자가 존재한다면,
하위 클래스의 저장 프로퍼티에 모든 값을 할당 한 후 super.init() 을 따로 넣지 않아도 됩니다.
(이는 자동으로 하위 클래스의 지정 생성자가 delegate up 상위 클래스의 지정생성자를 호출하기 때문)
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
Swift
복사
Automatic Initializer Inheritance
위에 언급한 바와같이 생성자는 기본적으로는 상속되지않습니다.
하지만, 특정 상황에서 생성자는 자동으로 상속됩니다.
즉, 몇몇 상황에서는 생성자를 override를 작성할 필요가 없음을 의미합니다.
Rules
1.
하위 클래스에 따로 지정 생성자를 정의하지 않은 경우
> 모든 지정생성자가 상속됨
2.
상위 클래스의 모든 지정생성자를 상속 받거나, 모든 지정 생성자를 override 하여 구현한 경우, 자동으로 > 모든 convenience init 을 상속됩니다.
Designated and Convenience Initializers in Action
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
//override 키워드를 작성하는 이유 => 상위 클래스에 동일한 파라미터의 생성자가 존재하기 때문.
//상위클래스의 지정 생성자를 상속
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
Swift
복사
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
Swift
복사
위 예시에서 상위클래스의 모든 지정 생성자가 override 를 통해 구현(규칙 2)되었으므로
Swift 는 자동으로 상위클래스의 convenience init 을 상속받는다.
따라서 RecipeIngredient() 를 통해 인스턴스 생성이 가능해진다.
더불어 init(name) 의 경우 상위 클래스에 동일한 생성자가 존재하므로 override 키워드를 작성해주어야 하며,
모든 프로퍼티에 대해 파라미터를 받지 않기 때문에 convenience 로 선언해준다.
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
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 ✔
// 1 x Bacon ✘
// 6 x Eggs ✘
Swift
복사
위 클래스의 경우 어떠한 생성자도 정의하지 않았기에
상위클래스의 모든 생성자를 상속받는다.
Failable Initializers
초기화 과정의 실패 조건을 처리하려면 init? 을 사용하자
유효하지 않은 파라미터 값, 필수적인 외부 자원의 부재 등등
단, 같은 이름과 타입의 파라미터를 가지는 nonfailable initializer 가 존재하는 경우 failable initializer 을 중복하여 정의가 불가능합니다.
failable init 은 옵셔널 값을 리턴합니다.
예외 조건에 따라 return nil 하면 됩니다,
엄밀하게 말하면 생성자는 값을 리턴하지 않는다. 이 경우 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"
Swift
복사
위 예시에서 Int 타입의 생성자 init(exactly:) 은 failable 생성자로 정확히 정수가 아닐 경우 생성에 실패합니다.
아래는 동물의 종이 파라미터로 전달되지 않았을 경우, 생성에 실패하도록 한 예제입니다.
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
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"
Swift
복사
주의 : nil 값과 “” 비어있는 문자열은 엄연히 다릅니다.
Falilable Initializers for Enumerations
failable init 을 하나 이상의 파라미터에 대해 적절한 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:
//어떠한 case 에도 해당되지 않는 경우 생성 실패
return nil
}
}
}
let fahrenheitUnit = TemperatureUnit(symbol: "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(symbol: "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."
Swift
복사
Failable Initializers for Enumerations with Raw Values
raw value 가 존재하는 열거형은 자동으로 falilable 생성자를 갖습니다
init?(rawValue:)
대응되는 rawValue 가 없을시 생성에 실패합니다.
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."
Swift
복사
Propagation of Initialization Failure
failable 한 init 내부에서 여타 다른 failable 한 init 을 호출할 수 있습니다.
failable 한 init 은 상위 클래스의 failable 한 init 을 호출할 수도 있습니다.
두 경우 모두, 일단 생성에 실패하면 더이상의 initialization 코드는 실행되지 않고 즉각 종료됩니다.
nonfailable 한 init 에게도 delegate 할 수 있습니다. 잠재적으로 실패 위험이 없는 경우에 해당 기능을 사용하세요
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)
}
}
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"
Swift
복사
위 예제에서 CarItem 은 quantity 값이 유효한지 먼저 확인한 후 프로퍼티에 저장합니다
그 후 상위 클래스의 failable init 을 호출하는 것을 확인할 수 있습니다.
CarItem 의 생성과정에서 quantity 값이 유효하지 않으면 상위클래스의 init 은 실행되지 않습니다.
Overriding a Failable Initializer
상위 클래스의 failable init 을 하위 클래스의 failable init 으로 override 할 수 있습니다.
상위 클래스의 failable init 을 하위 클래스의 nofailable init 으로 override 할 수 있습니다.
이 경우, 생성 과정을 실패하지 않습니다. (Forced unwrap 이 필요하기 때문.)
class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// 항상 name 을 가지는 인스턴스를 생성
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
//failable 을 override 했지만 실패하지 않음.
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]")!
}
}
Swift
복사
상위 클래스의 failable init 을 override 하는 법
•
똑같이 failable init 으로 override
•
nonfailable init 으로 override
◦
이 경우 상위 클래스의 nonfailable init 을 호출 해주어야함.
◦
상위 클래스 와는 다른 방식으로 생성 실패에 대응 해주어야함.
상위 클래스의 failable init 을 하위클래스의 failable init 에서 호출
반드시 생성에 실패하지 않는 조건 선에서 forced unwrap 해주어야함.
The init! Failable Initializer
init! 을 사용해 암묵적으로 unwrap 된 optional 인스턴스를 생성가능합니다.
init? 에서 init! 으로 delegate 가능하며 그 반대도 가능합니다.
Required Initializers
required modifier 를 사용해 하위 클래스에서 해당 생성자를 필수 구현사항으로 설정할 수 있습니다.
class SomeClass {
required init() {
// initializer implementation goes here
}
}
class SomeSubclass: SomeClass {
required init() {
super.init()
// subclass implementation of the required initializer goes here
}
}
Swift
복사
하위 클래스에서 required init() 과 super.init() 의 사용이 강제됩니다
일반적으로 개발자가 required init자체를 직접 만들어서 구현해야 하는 경우는 매우 드문 경우입니다.
UIButton같은것들이 보통 그런데 이런것들을 상속받아 사용할거면
required init은 반드시 구현해서 접근할 수 있게 해놔야 정상적으로 프로그램이 작동되도록 설계되어 있다고 이해하시면 됩니다.
Setting a Dafault Property Value with a Closure or Function
저장 프로퍼티가 커스터마이징이 필요한 경우 closure 또는 global function 을 사용해 초기값을 커스터 마이징할 수 있습니다
아래는 skeleton code 입니다.
클로져를 사용해서 프로퍼티의 값을 설정하는 경우, 다른 프로퍼티들의 initialize 는 아직 실행되지 않았음에 주의합니다!!!
즉, 해당 클로져 내부에서는 다른 프로퍼티에 접근할 수 없습니다.
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
}()
}
Swift
복사
체스보드의 색이 채워져 있는 경우 true 비워져있는 경우 false 라 하자
체스보드 칸의 색을 나타내는 array 를 프로퍼티를 선언하려 하는데,
체스보드 칸은 64 개라 이를 모두 일일히 초기값을 설정해주기는 어렵다
따라서 클로져를 사용해서 해당 프로퍼티의 초기값을 선언하겠다.
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"
Swift
복사
새 인스턴스가 생성될 때, 클로져가 실행되며, boardColors 의 초기값이 계산되고 리턴된다.