Swift/Swift 가이드

[Swift] 공식 문서 - 초기화(Initialization)

유정주 2022. 7. 26. 13:58
반응형

새로 배운 점

  • 상수 프로퍼티는 이니셜라이저 안에서도 초기화할 수 있다.
  • 구조체의 기본 이니셜라이저가 Memberwise Initializers라는 이름이라는 것
  • designated 이니셜라이저는 해당 클래스가 직접 상속받는 슈퍼클래스의 designated 이니셜라이저를 호출해야 한다.
  • convenience 이니셜라이저는 같은 클래스의 다른 이니셜라이저만 호출해야 한다.
  • convenience 이니셜라이저는 궁극적으로 designated 이니셜라이저를 호출해야 한다.
  • Swift의 컴파일러는 2단계 초기화를 에러 없이 완료하기 위해 4가지 safety-check를 수행한다.
  • 서브클래스에서 새로 추가한 모든 프로퍼티에 기본값이 지정되면, 두 가지 규칙이 적용된다.
    • 만약 서브클래스가 어떠한 designated 이니셜라이저도 정의하지 않는다면, 자동으로 슈퍼클래스의 모든 designated 이니셜라이저는 상속된다.
    • 서브클래스가 슈퍼클래스의 모든 designated 이니셜라이저를 구현한 경우(또는 1번 규칙을 만족한 경우), 슈퍼클래스의 모든 convenience 이니셜라이저는 상속된다.
  • 클래스, 구조체, 열거형에서 실패할 수 있는 초기화를 정의할 수 있다.
  • raw 값을 가지는 열거형은 자동으로 failable initializer, init?(rawValue:)를 생성한다.
  • 클래스의 서브 클래스가 해당 이니셜라이저를 필수로 구현하도록 하려면 required 수식어를 클래스 이니셜라이저 앞에 붙여줍니다.
  • 프로퍼티를 초기화할 때 클로저를 사용한다면, 인스턴스의 다른 프로퍼티들은 클로저가 수행되는 시점에서 초기화가 완료된 것이 아닙니다.

 

Initialization

초기화(Initialization)는 클래스, 구조체, 열거형의 인스턴스를 사용하기 위해 준비하는 과정입니다.

이 과정에는 인스턴스의 각 저장 프로퍼티의 초기값을 세팅하고,

새로운 인스턴스를 사용하기 전에 필요한 다른 setup이나 초기화를 수행합니다.

 

초기화는 이니셜라이저(initializers)를 정의하여 구현할 수 있는데, 이는 새로운 인스턴스를 만들 때 호출하는 메서드와 같습니다.

Objective-C의 이니셜라이저와 이니셜라이저 달리, Swift의 이니셜라이저는 값을 반환하지 않습니다.

이니셜라이저의 주요 역할은 새로운 인스턴스를 처음 사용하기 전에 올바르게 초기화하는 것입니다.

 

클래스 타입의 인스턴스는 소멸자(deinitializer)를 구현할 수 있는데,

이는 클래스의 인스턴스의 할당 해제 전 커스텀 cleanup을 수행합니다.

(소멸자는 다음 포스팅에서 다룹니다.)

 

Setting Initial Values for Stored Properties

클래스나 구조체는 그 인스턴스가 생성될 때, 반드시 모든 저장 프로퍼티에 적절한 초기값을 설정해줘야 합니다.

init 키워드를 사용해 이니셜라이저를 작성합니다.

init() {
    // perform some initialization here
}

아래 예제 코드는 Fahrenheit라는 구조체를 정의합니다.

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"

구조체는 파라미터가 없는 생성이니셜라이저자를 가지고 있고, temperature의 값을 32.0으로 초기화합니다.

 

Default Property Values

저장 프로퍼티의 초기값은 이니셜라이저 안에서 설정할 수 있지만,

프로퍼티 정의 부분에 기본 프로퍼티 값을 지정하여 초기화할 수도 있습니다.

 

기본 값은 프로퍼티를 정의할 때 초기값을 할당해서 설정할 수 있습니다.

프로퍼티가 항상 같은 초기값을 사용하는 경우 이니셜라이저 내에서 초기화하는 것보다 기본값을 설정하는 것이 좋습니다.
결과는 동일하지만 이니셜라이저를 더 짧고 명확하게 작성할 수 있고, 기본값을 통해 타입을 추론할 수도 있습니다.
기본값을 사용하면 default 이니셜라이저와 이니셜라이저 상속(initializer inheritance)을 더 쉽게 사용할 수 있습니다.

위에서 본 Fahrenheit처럼 구조체는 temperature 속성을 선언할 때 기본값을 할당하여

더 간단하게 초기화를 할 수 있습니다.

struct Fahrenheit {
    var temperature = 32.0
}

 

Customizing Initialization

Initialization Parameters

이니셜라이저의 정의 부분에서 초기화 파라미터(initialization parameters)를 전달하여 초기화를 커스터마이징할 수 있습니다.

초기화 파라미터는 함수와 메서드의 파라미터와 동일하며, 동일한 문법으로 작성됩니다.

 

아래 예제는 Celsius 구조체를 정의합니다.

온도 단위에 따라 다른 이니셜라이저를 제공합니다.

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

두 이니셜라이저는 전달받은 인자를 대응되는 화씨값을 변환하고

변환된 값을 temperatureInCelsius에 저장합니다.

 

Parameter Names and Arguments Labels

초기화 파라미터도 이니셜라이저 body에서 사용하는 파라미터 이름과

이니셜라이저를 호출할 때 사용되는 인자 이름(argument label)을 갖습니다.

그러나 이니셜라이저는 함수와 메서드처럼 괄호 앞에 식별할 수 있는 이름이 없습니다.

따라서 이니셜라이저의 파라미터 이름과 타입은 호출해야하는 이니셜라이저를 구분하는데 중요한 역할을 합니다.

이러한 이유로 인자 이름(argument label)이 제공되지 않을 경우 Swift는 자동으로 모든 파라미터의 인자 이름을 제공합니다.

 

아래 예제 코드는 Color라는 구조체를 정의하고 있습니다.

Color는 Double 타입인 3개의 파라미터를 가진 이니셜라이저를 제공합니다.

또한 Color는 하나의 white 파라미터를 가진 두 번째 이니셜라이저도 제공하며, 이는 3가지 색상 요소를 모두 같은 값으로 초기화합니다.

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
    }
}

두 이니셜라이저는 각 이니셜라이저의 파라미터를 사용해 새로운 Color 인스턴스를 만드는데 사용됩니다.

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

인자 이름을 사용하지 않고 이니셜라이저를 호출하는 것은 불가능합니다.

인자 이름은 정의된 경우 항상 이니셜라이저에서 사용되어야 하며, 이를 생략하면 컴파일 에러가 발생합니다.

let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required

 

Initializer Parameters Without Argument Labels

만약 이니셜라이저 파라미터에 인자 이름을 사용하고 싶지 않다면, underscore(_)를 argument label 대신 사용하면 됩니다.

아래 예시는 underscore를 이용한 이니셜라이저입니다.

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

옵셔널 타입의 속성을 선언하여 커스텀 타입이 값이 없음을 가지는 저장 프로퍼티를 정의할 수 있습니다.

옵셔널 타입의 프로퍼티는 자동으로 nil로 초기화되며

이는 초기화 중에 해당 속성이 아직 값이 없다는 것을 의미합니다.

 

아래 예제는 옵셔널 String 프로퍼티인 response를 가진 SurveyQuestion 클래스를 정의하고 있습니다.

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."

질문에 대한 응답은 질문을 하기 전까지 알 수 없습니다.

따라서 response는 String? 타입으로 선언됩니다.

이는 자동으로 niil로 초기화됩니다.

 

Assigning Constant Properties During Initialization

상수(let) 프로퍼티는 초기화 중 어느 시점에서도 값이 할당될 수 있지만,

값이 한 번 할당되면 값을 수정할 수 없습니다.

클래스 인스턴스의 경우 상수 프로퍼티는 해당 프로퍼티를 정의한 클래스에 의해서만 초기화할 수 있습니다.
서브클래스에서는 수정할 수 없습니다.

위에서 살펴본 ServeyQuestion 구조체의 text 프로퍼티를 상수로 변경했습니다.

상수로 변경했지만 클래스의 이니셜라이저에서 값이 설정될 수 있습니다.

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

Swift는 모든 프로퍼티에 기본값을 제공하고

이니셜라이저가 하나도 없는 구조체나 클래스에 default 이니셜라이저를 제공합니다.

default 이니셜라이저는 모든 프로퍼티에 기본값을 설정하고 새로운 인스턴스를 생성합니다.

 

아래 예제는 ShoppingListItem 클래스를 정의합니다.

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

ShoppingListItem 클래스는 모든 프로퍼티가 기본값을 가지고 있고,

슈퍼클래스가 아닌 기본 클래스이기 때문에 자동으로 기본 이니셜라이저를 제공합니다.

 

Memberwise Initializers for Structure Types

구조체 타입은 어떠한 커스텀 이니셜라이저도 정의되지 않은 경우에

자동으로 멤버와이즈(memberwise) 이니셜라이저를 제공합니다.

기본 이니셜라이저와는 다르게 구조체는 기본값이 없는 저장 프로퍼티가 있더라도 멤버와이즈 이니셜라이저를 제공합니다.

멤버와이즈 이니셜라이저는 새로운 구조체 인스턴스의 멤버 프로퍼티들을 초기화하는 간단한 방법입니다.

새로운 인스턴스의 프로퍼티들의 초기값을 멤버와이즈 이니셜라이저로 전달되면 됩니다.

 

아래 예제는 width와 height 속성을 가지고 있는 Size 구조체입니다.

Size 구조체는 자동으로 init(width:height:) 멤버와이즈 이니셜라이저를 생성합니다.

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

멤버와이즈 이니셜라이저를 호출할 때, 기본값이 있는 프로퍼티는 생략할 수 있습니다.

위 예제에서 Size 구조체는 width와 height 모두 기본값을 가지고 있습니다.

따라서 아래와 같이 해당 프로퍼티를 생략할 수 있고, 생략하면 기본값으로 초기화됩니다.

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

이니셜라이저에서 다른 이니셜라이저를 호출할 수 있는데, 이 과정을 initializer delegation이라고 합니다.

이는 여러 이니셜라이저를 사용할 때 코드의 중복을 피하기 위해 사용됩니다.

 

initializer delegation의 동작 방식과 허용되는 delegation 형식은 값 타입과 class 타입에 따라 다릅니다.

값 타입에서는 상속이 지원되지 않아 직접 제공하는 이니셜라이저에 대해서만 delegation이 가능하므로

값 타입에서는 initializer delegation이 비교적 단순합니다. 그러나 클래스는 다른 클래스로부터 상속 받을 수 있습니다.

즉, 클래스는 초기화 중에 상속되는 모든 저장 프로퍼티에 적절한 값이 할당되도록 해야합니다.

이에 대해 아래에서 설명합니다.

 

값 타입은 self.init을 사용해 다른 이니셜라이저를 참조할 수 있습니다.

self.init은 이니셜라이저 내부에서만 호출할 수 있습니다.

 

값 타입의 커스텀 이니셜라이저를 정의한다면,

더이상 해당 타입에서 기본 이니셜라이저 또는 멤버와이즈 이니셜라이저를 참조할 수 없습니다.

이는 실수로 자동으로 생성되는 이니셜라이저를 사용하는 상황을 방지합니다.

만약 커스텀 이니셜라이저와 default/memberwise 이니셜라이저를 같이 사용하고 싶다면,
extension을 사용해 커스텀 이니셜라이저를 작성하면 됩니다.
이는 extension 글에서 자세히 다룹니다.

아래 예제는 사각형을 표현하는 Rect 구조체를 정의합니다.

이 예제에서는 Size와 Point라는 두 개의 구조체가 사용되며, 두 구조체에서의 모든 속성을 0.0을 기본값으로 갖습니다.

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

Rect 구조체는 3가지 방법으로 초기화할 수 있습니다.

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)
    }
}

 

첫 번째 Rect 이니셜라이저인 init()은 기본 이니셜라이저와 같습니다.

이 이니셜라이저를 호출하면 origin과 size가 Point(x: 0.0, y: 0.0)과 Size(width:0.0, height: 0.0)으로 초기화됩니다.

let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

 

두 번째 Rect 이니셜라이저인 init(origin:size:)는 멤버와이즈 이니셜라이저와 동일합니다.

이 이니셜라이저는 간단하게 origin와 size를 초기화합니다.

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)

 

세 번째 Rect 이니셜라이저인 init(center:size:)는 다소 복잡합니다.

center Point와 size 값을 통해 원점을 계산하고 init(origin:size:) 이니셜라이저를 호출(delegates)하여 초기화합니다.

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

모든 클래스의 저장 프로퍼티는 반드시 초기화 중에 초기값이 할당돼야 합니다.

Swift는 클래스 타입에서 모든 저장 프로퍼티가 초기값을 갖도록 하는 두 종류의 이니셜라이저를 정의합니다.

지정(designated) 이니셜라이저와 편의(convenience) 이니셜라이저입니다.

 

Designated Initializers and Convenience Initializers

Designated 이니셜라이저는 클래스의 기본 이니셜라이저입니다.

Designated 이니셜라이저는 클래스에서 정의된 모든 프로퍼티를 초기화하고

적절한 슈퍼클래스의 이니셜라이저를 호출하여 슈퍼클래스와 연관된 초기화 작업을 수행합니다.

 

클래스에서 Designated 이니셜라이저는 하나만 가지고 있는 것이 일반적입니다.

Designated 이니셜라이저는 초기화 프로세스가 슈퍼클래스로 이어지게 해주는 funnel 포인트라고 할 수 있습니다.

모든 클래스는 적어도 하나의 Designated 이니셜라이저를 가져야합니다.

몇몇 클래스에서 이 제한은 슈퍼클래스로부터 하나 이상의 Designated 이니셜라이저를 상속 받아서 만족할 수 있습니다.

이 내용은 Automatic Initializer Inheritance 문단에서 다시 설명합니다.

 

Convenience 이니셜라이저는 클래스에서 지원하는 두 번째 이니셜라이저입니다.

Convenience 이니셜라이저는 같은 클래스로부터 designated 이니셜라이저를 호출하여 정의할 수 있는데,

designated 이니셜라이저의 일부 파라미터에 기본값이 설정된 것이 Convenience 이니셜라이저라고 생각하면 됩니다.

또한, 해당 클래스의 인스턴스를 특정 use 케이스나 특정 input value 타입에 대해 생성하는 Convenience 이니셜라이저를 정의할 수 있습니다.

필요가 없다면 Convenience 이니셜라이저는 정의하지 않아도 됩니다.

 

Syntax for Designated and Convenience Initializers

Designated 이니셜라이저의 작성 방법입니다.

init(parameters) {
    statements
}

 

Convenience 이니셜라이저는 convenience 수식어를 init 키워드 앞에 붙여 작성합니다.

convenience init(parameters) {
    statements
}

 

Initializer Delegation for Class Types

Designated와 convenience 이니셜라이저의 관계를 간단하게 하기 위해

Swift는 이니셜라이저 간의 delegation 호출을 위한 3가지 규칙을 적용합니다.

  1. designated 이니셜라이저는 해당 클래스가 직접 상속받는 슈퍼클래스의 designated 이니셜라이저를 호출해야 한다.
  2. convenience 이니셜라이저는 같은 클래스의 다른 이니셜라이저만 호출해야 한다.
  3. convenience 이니셜라이저는 궁극적으로 designated 이니셜라이저를 호출해야 한다.

이를 요약하면 아래처럼 정리할 수 있습니다.

  1. designated 이니셜라이저는 반드시 슈퍼클래스를 delegation 해야한다.
  2. convenience 이니셜라이저는 반드시 같은 레벨에서 delegate 해야한다.

 

이 규칙은 아래 그림에서 쉽게 설명합니다.

위 그림에서 서브클래스의 convenience 이니셜라이저는 같은 클래스에서 다른 designated 이니셜라이저를 호출하고

designated 이니셜라이저는 슈퍼클래스의 designated 이니셜라이저를 호출합니다.

슈퍼 클래스에서 한 convenience 이니셜라이저는 다른 convenience 이니셜라이저를 호출하고

이는 designated 이니셜라이저를 호출합니다.

(위와 형태는 달라도 되지만 규칙은 지켜야 합니다.)

 

다음은 조금 더 복잡한 형태의 초기화 delegation 입니다.

 

Two-Phase Initialization

Swift에서 클래스 초기화는 2단계로 이루어집니다.

첫 번째 단계에서 각 저장 프로퍼티는 초기값으로 초기화됩니다.

모든 저장 프로퍼티가 초기화되면 두 번째 단계가 시작됩니다.

두 번째 단계는 각 클래스가 사용 준비가 완료되기 전에 저장 프로퍼티를 커스터마이징합니다.

 

2단계의 초기화 과정은 초기화를 safe하게 해주면서 클래스 계층 구조에서 각 클래스에 완전한 flexibility를 제공합니다.

2단계의 초기화는 프로퍼티 값들이 초기화 전에 참조되는 것을 막아주고,

프로퍼티 값이 예기치 않게 다른 이니셜라이저에 의해 다른 값으로 설정되는 것을 막아줍니다.

Swift의 컴파일러는 2단계 초기화를 에러 없이 완료하기 위해 4가지 safety-check를 수행합니다.

  1. designated 이니셜라이저는 슈퍼클래스의 이니셜라이저에 delegate 하기 전에 클래스의 모든 속성을 초기화해야 합니다.
    객체의 메모리는 모든 저장 프로퍼티가 초기 상태를 갖추어야 완전히 초기화된 것으로 간주합니다.
    따라서, 이 조건을 만족하기 위해서 designated 이니셜라이저는 반드시 다른 이니셜라이저로 넘어가기 전에 소유한 모든 프로퍼티를 초기화해야 합니다.
  2. designated 이니셜라이저는 상속된 프로퍼티를 할당하기 전에 반드시 슈퍼클래스 이니셜라이저로 delegate 해야 합니다.
    만약 그렇지 않으면 designated 이니셜라이저가 할당한 새로운 값은 슈퍼클래스의 이니셜라이저에 의해 덮어씌워지게 됩니다.
  3. convenience 이니셜라이저는 어떤 프로퍼티의 값을 할당하기 전에 다른 이니셜라이저로 delegate 해야 합니다.
    만약 그렇지 않으면 convenience 이니셜라이저가 할당한 새로운 값은 그 클래스의 designated 이니셜라이저에 의해 덮어씌워지게 됩니다.
  4. 이니셜라이저는 1단계 초기화가 끝나기 전에 self의 값을 참조하거나 어떤 인스턴스 프로퍼티에 접근하거나 메서드를 호출할 수 없습니다.

다음은 2단계 초기화가 위의 4가지 safety check를 기반으로 어떻게 동작하는지 보여줍니다.

- Phase 1

  • designated나 convenience 이니셜라이저가 클래스에서 호출됨
  • 그 클래스의 새로운 인스턴스를 위한 메모리가 할당되고, 아직 메모리는 초기화되지 않음
  • 해당 클래스의 designated 이니셜라이저는 모든 저장 프로퍼티가 기본값을 가지는지 확인하고,
    저장 프로퍼티의 메모리가 초기화 됨
  • 상속 chain의 최상위에 도달할 때까지 계속 수행됨
  • 최상위까지 도달하고 final 클래스가 모든 저장 프로퍼티의 값을 가지면, 인스턴스 메모리는 완전히 초기화된 거승로 간주되고
    1단계가 완료됨

- Phase 2

  • 최상위부터 내려가면서 각 designated 이니셜라이저는 인스턴스를 커스터마이징할 수 있음.
    이니셜라이저는 이제 self에 액세스할 수 있고, 프로퍼티들을 수정하거나 인스턴스 메서드를 호출할 수 있음
  • 마지막으로 convenience 이니셜라이저는 self를 사용하여 인스턴스를 커스터마이즈할 수 있음

아래 이미지는 가상의 서브클래스와 슈퍼클래스에서의 초기화 호출을 보여줍니다.

여기서 서브클래스의 convenience 이니셜라이저의 호출로 초기화가 시작됩니다.

이 convenience 이니셜라이저는 아직 어떠한 프로퍼티도 수정할 수 없습니다.

이는 같은 클래스의 designated 이니셜라이저에게 delegate 합니다.

 

designated 이니셜라이저는 서브클래스의 모든 프로퍼티가 값을 가지는지 확인합니다.(safety check1)

그리고 슈퍼클래스의 designated 이니셜라이저를 호출하여 연결된 초기화를 계속 수행합니다.

슈퍼클래스의 designated 이니셜라이저는 슈퍼클래스의 모든 프로퍼티가 값을 가지는지 확인합니다.

더이상 초기화할 슈퍼클래스들이 없다면 더이상 delegation은 없습니다.

슈퍼클래스의 모든 프로퍼티들이 초기값을 가지면, 메모리는 완전히 초기화된 것으로 간주되고 1단계 초기화가 완료됩니다.

 

다음 그림은 동일한 초기화 호출에서의 2단계 초기화를 보여줍니다.

슈퍼클래스의 desigated 이니셜라이저는 할 필요가 없어도 인스턴스를 커스터마이징할 기회를 가집니다.

슈퍼클래스의 designated 이니셜라이저가 끝나면 서브클래스의 designated 이니셜라이저가 추가적인 커스터마이징을 수행합니다.

마지막으로 서브클래스의 designated 이니셜라이저가 끝나면 처음 호출되었던 convenience 이니셜라이저가 추가 커스터마이즈를 수행합니다.

 

Initializer Inheritance and Overriding

Objective-C와는 달리 Swift의 서브클래스들은 슈퍼클래스의 이니셜라이저를 기본적으로 상속하지 않습니다.

이는 이니셜라이저 초기화로부터 서브클래스의 인스턴스들이 잘못 초기화되는 것을 막기 위함입니다.

슈퍼클래스의 이니셜라이저는 안전하거나 적절한 특정 상황에서는 상속됩니다.
자세한 내용은 Automatic Initializer Inheritance 문단에서 설명합니다.

만약 커스텀 서브클래스가 슈퍼클래스와 동일한 이니셜라이저 중의 하나 이상을 사용하도록 하려면

서브클래스에서 해당 이니셜라이저의 커스텀 구현을 제공하면 됩니다.

 

슈퍼클래스의 designated 이니셜라이저와 일치하는 서브클래스의 이니셜라이저를 작성하면

deisignated 이니셜라이저의 override한 것과 같습니다.

그러므로 서브클래스의 이니셜라이저 정의에서 override 수식어를 추가해줘야 합니다.

자동으로 생성되는 기본 이니셜라이저를 오버라이드할 때에도 마찬가지입니다.

 

오버라이드된 프로퍼티, 메서드, 서브스크립트처럼 override 수식어가 있으면

Swift는 슈퍼클래스에서 일치하는 designated 이니셜라이저가 있는지 체크합니다.

 

반대로 슈퍼클래스의 convenience 이니셜라이저와 일치하는 서브클래스의 이니셜라이저를 작성한다면,

위에서 설명한 Initializer Delegation for Class Types의 규칙에 따라

해당 슈퍼클래스의 convenience 이니셜라이저는 서브클래스에서 직접 호출할 수 없습니다.

따라서 서브클래스는 슈퍼클래스의 이니셜라이저의 오버라이드를 제공하지 않습니다.

결과적으로 슈퍼클래스의 convenience 이니셜라이저와 일치하는 이니셜라이저를 구현할 때에는

override 수식어가 필요 없습니다.

 

아래 예제는 Vehicle이라는 기본 클래스를 정의합니다.

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

Vehicle 클래스는 오직 저장 프로퍼티에 대한 기본값만 제공하고 자체 커스텀 이니셜라이저는 없습니다.

따라서 자동으로 기본 이니셜라이저가 생성됩니다.

기본 이니셜라이저는 항상 designated 이니셜라이저이고, 새로운 Vehicle 인스턴스를 생성할 때 사용됩니다.

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

 

다음은 Vehicle의 서브클래스인 Bicycle입니다.

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

Bicycle 서브클래스는 커스텀 designated 이니셜라이저 init()을 정의합니다.

이 designated 이니셜라이저는 Bicycle의 슈퍼클래스인 Vehicle의 designated 이니셜라이저와 일치하고,

따라서 Bicycle 버전의 이니셜라이저에 override 수식어가 붙습니다.

 

Bicycle의 init() 이니셜라이저는 먼저 super.init()을 호출하며,

이는 Bicycle 클래스의 수퍼클래스의 default 이니셜라이저를 호출합니다.

이는 상속 받은 numberOfWheels 속성을 Bicycle이 수정할 기회를 얻기 전에 Vehicle에서 초기화합니다.

super.init()이 호출된 이후에 numberOfWheels 값은 새로운 값인 2로 변경됩니다.

 

Bicycle의 인스턴스를 새롭게 만들면,

상속받은 description 연산 플퍼티를 호출해 numberOfWheels의 값이 업데이트된 것을 확인할 수 있습니다.

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)

 

만약 서브클래스 이니셜라이저가 초기화 2단계에서 어떠한 커스터마이징도 안 하고,

슈퍼클래스는 argument가 없는 designated 이니셜라이저를 갖는다면,

서브클래스의 모든 저장 프로퍼티의 값을 할당한 후에 super.init() 호출을 생략할 수 있습니다.

(슈퍼클래스에서 상속받은 속성을 수정하려면 생략하면 안 됩니다.)

 

아래는 Hoverboard라는 Vehicle의 서브클래스를 정의합니다.

Hoverboard 이니셜라이저에서는 color 프로퍼티만 설정하고 있습니다.

super.init()을 명시적으로 호출하는 대신,

이 동작이 완료된 후에 슈퍼클래스 이니셜라이저가 암시적으로 호출됩니다.

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() implicitly called here
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}

Hoverboard의 인스턴스는 Vehicle 이니셜라이저에서 제공되는 기본값을 사용합니다.

let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver

 

Automatic Initializer Inheritance

위에서 말했듯이, 서브클래스는 기본적으로 슈퍼클래스의 이니셜라이저를 상속 받지 않습니다.

그러나 슈퍼클래스의 이니셜라이저는 특정 상황이 만족되면 자동으로 상속됩니다.

실제로 많은 상황에서 override를 작성할 필요가 없으며 안전하다면 얼마든지 슈퍼클래스의 이니셜라이저를 상속 받을 수 있습니다.

 

서브클래스에서 새로 추가한 모든 프로퍼티에 기본값이 지정되면, 다음의 두 가지 규칙이 적용됩니다.

  1. 만약 서브클래스가 어떠한 designated 이니셜라이저도 정의하지 않는다면,
    자동으로 슈퍼클래스의 모든 designated 이니셜라이저는 상속된다.
  2. 서브클래스가 슈퍼클래스의 모든 designated 이니셜라이저를 구현한 경우(또는 1번 규칙을 만족한 경우),
    슈퍼클래스의 모든 convenience 이니셜라이저는 상속된다.

이 규칙들은 서브클래스에 추가 convenience 이니셜라이저를 추가하는 경우에도 적용됩니다.

 

Designated and Convenience Initializers in Action

예제를 통해 desingated, convenience 이니셜라이저와 자동 이니셜라이저 상속이 어떻게 쓰이는지 보겠습니다.

이 예제에서는 Food, RecipeIngredient, ShoppingListItem 이라는 3개의 클래스 계층 구조를 가집니다.

이들의 이니셜라이저들이 어떻게 상호작용하는지 설명합니다.

 

이 계층 구조의 기본 클래스는 Food입니다.

Food는 하나의 String 프로퍼티를 가지고 있으며 2개의 이니셜라이저가 있습니다.

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

아래 그림은 Food 클래스의 초기화 chain 입니다.

클래스들은 기본 멤버와이즈 이니셜라이저가 없습니다.

그래서 Food 클래스는 name이라는 하나의 argument를 가지는 designated 이니셜라이저를 제공합니다.

이 이니셜라이저는 새로운 Food 인스턴스를 생성하는데 사용됩니다.

let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"

 

두 번째 클래스는 Food의 서브클래스인 RecipeIngredient입니다.

이 클래스는 요리 레시피의 재료를 모델링합니다.

quantity Int형 프로퍼티를 가지고 있고 두 개의 이니셜라이저가 있습니다.

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 클래스의 이니셜라이저 chain을 보여줍니다.

RecipeIngredient 클래스는 하나의 designated 이니셜라이저, init(name:quantity:)를 가집니다.

이니셜라이저는 Food 클래스의 init(name:) 이니셜라이저로 delegate 합니다. (safe check 1)

 

RecipeIngredient는 convenience 이니셜라이저인 init(name:)을 정의합니다.

이 convenience 이니셜라이저는 quantity를 1로 가정하고 인스턴스를 생성합니다.

convenience 이니셜라이저를 이용하면 RecipeIngredient 인스턴스를 더 빠르고 편리하게 생성할 수 있고,

불필요한 코드 중복을 방지할 수 있습니다.

 

init(name:)의 convenience 이니셜라이저는 Food의 init(name) designated 이니셜라이저와 동일한 파라미터를 가집니다.

이 convenience 이니셜라이저는 슈퍼클래스의 designated 이니셜라이저를 오버라이드하고 있으므로

반드시 override 수식어가 있어야 합니다.

 

비록 RecipeIngredient는 init(name:)을 convenience 이니셜라이저로 제공하지만,

슈퍼클래스의 모든 designated 이니셜라이저 구현을 제공합니다.

즉, RecipeIngredient는 자동으로 슈퍼클래스의 모든 convenience 이니셜라이저를 상속합니다.

 

이 예제에서 RecipeIngredient의 슈퍼클래스는 Food이고,

Food 클래스는 init()이라는 하나의 convenience 이니셜라이저를 가지고 있습니다.

그러므로 이 이니셜라이저는 RecipeIngredient에 상속됩니다.

상속된 버전의 init() 함수는 Food 버전이 아닌 RecipeIngredient 버전의

init(name:)에 delegate 한다는 것만 제외하면 Food와 완전히 동일하게 동작합니다.

 

아래의 3가지 초기화는 새로운 RecipeIngredient 인스턴스를 생성하는데 사용합니다.

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

첫 번째 줄의 아무런 인자가 없는 초기화는 RecipeIngredient의 convenience 이니셜라이저를 호출합니다.

 

계층 구조에서 세 번째인 마지막 클래스는 RecipeIngredient의 서브클래스인 ShoppingListItem입니다.

이 클래스는 쇼핑리스트의 레시피 재료를 모델링합니다.

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}

모든 프로퍼티에 기본값을 정의하고 있으므로,

자체적인 이니셜라이저는 정의하지 않고 있습니다.

따라서 ShoppingListItem은 자동으로 슈퍼클래스의 모든 designated와 convenience 이니셜라이저를 상속 받습니다.

 

그림으로 표현하면 다음과 같습니다.

ShoopingListItem은 상속 받은 3개의 이니셜라이저를 사용하여 생성할 수 있습니다.

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 ✘

 

Failable Initializers

클래스, 구조체, 열거형에서 실패할 수 있는 초기화를 정의할 수 있습니다.

유효하지 않은 초기화 파라미터 값이나, 필요한 외부 리소스가 없거나,

초기화를 성공할 수 없는 다른 조건들로 인해 초기화에 실패할 수 있습니다.

 

초기화 실패에 대응하기 위해 클래스, 구조체, 열거형을 정의할 때

하나 이상의 failable 이니셜라이저를 정의해야 합니다.

Failable 이니셜라이저는 init 키워드 뒤에 물음표(?)를 붙여서 정의할 수 있습니다.

 

같은 파라미터 타입과 이름을 갖는 failable 이니셜라이저와 nonfailable 이니셜라이저를 정의할 수 없습니다.
두 이니셜라이저는 파라미터로 구분되기 때문에 동시에 사용할 수 없습니다.

 

Failable 이니셜라이저는 초기화하는 타입의 옵셔널 value를 생성합니다.

failable 이니셜라이저 내에서 return nil을 작성해서 초기화 실패했다는 것을 트리거할 수 있습니다.

 

사실 이니셜라이저는 실제로 값을 반환하지 않는데 이니셜라이저의 역할은 완전하게 초기화되도록 하는 것입니다.
return nil이 초기화 실패를 트리거하지만 초기화 성공은 return 하지 않습니다.

 

예를 들어 failable 이니셜라이저는 숫자 타입 변환에서 구현됩니다.

숫자 타입간 변화를 통해 값을 정확하게 유지하려면, init(exactly:) 이니셜라이저를 사용하면 됩니다.

만약 타입 변환이 값을 유지할 수 없다면, 이니셜라이저는 실패합니다.

 

다음 코드는 failable 이니셜라이저를 사용한 예제입니다.

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"

 

아래 예제 코드는 Animal 구조체를 정의하고 있습니다.

Animal 구조체는 failable 이니셜라이저를 정의합니다.

이 이니셜라이저는 이니셜라이저로 전달되는

species 값이 빈 문자열인지 체크하고, 빈 문자열이라면 초기화 실패가 트리거됩니다.

species 값이 설정되면 초기화는 성공입니다.

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

 

아래는 새로운 Animal 인스턴스 초기화를 failable 이니셜라이저를 사용한 예제 코드입니다.

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"

만약 failable 이니셜라이저의 species 파라미터에 빈 문자열을 전달하면

초기화 실패가 트리거 됩니다.

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"

 

Failable Initializers for Enumerations

하나 이상의 파라미터에서 적절한 열거형 케이스를 선택하는

failable 이니셜라이저를 사용할 수 있습니다.

이 이니셜라이저는 적절한 열거형 케이스를 선택할 수 없다면 초기화 실패합니다.

 

아래 예제는 TemperatureUnit 열거형을 정의합니다.

여기에 가능한 3개의 상태가 있습니다.

Failable 이니셜라이저는 온도 기호를 표현하는 Character 값으로 적절한 열거 케이스를 찾습니다.

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("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."

 

Failable Initializers for Enumerations with Raw Values

raw 값을 가지는 열거형은 자동으로 failable initializer, init?(rawValue:)를 생성합니다.

이 이니셜라이저는 rawValue라는 파라미터를 전달받고, 일치하는 열거형 케이스를 선택하는데

만약 매칭되는 값이 없다면 초기화 실패를 트리거합니다.

 

아래 코드는 위에서 살펴본 TemperatureUnit 열거형을 Character 타입의 raw 값을 가지도록 수정합니다.

따라서 아래 코드에서는 init?(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."

 

Propagation of Initialization Failure

클래스, 구조체, 열거형의 Failable 이니셜라이저는 동일한 클래스, 구조체, 열거형의

다른 failable 이니셜라이저로 delegate 할 수 있습니다.

마찬가지로 서브클래스의 failable 이니셜라이저도 슈퍼클래스의 failable 이니셜라이저로 delegate 할 수 있습니다.

 

두 경우에서, 만약 초기화 실패를 야기하는 다른 이니셜라이저로 delegate 한다면,

전체 초기화 과정은 즉시 실패하고 더 이상 초기화 코드가 실행되지 않습니다.

failable 이니셜라이저는 nonfailable 이니셜라이저에게 delegate할 수도 있습니다.

 

다음 예제는 CartItem 이라는 Product의 서브클래스를 정의하고 있습니다.

CartItem 클래스는 온라인 쇼핑카트의 항목을 모델링하며,

quantity라는 저장 상수 프로퍼티를 가지고 있고 이 값은 항상 1 이상입니다.

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)
    }
}

quantity 값이 1 미만이라면 즉시 초기화 실패하고, 이후의 초기화 코드는 수행되지 않습니다.

Product의 failable 이니셜라이저는 name이 비어있을 때 즉시 초기화 실패합니다.

비어 있지 않은 name이고 quantity가 1 이상이라면 초기화 성공합니다.

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"

 

Overriding a Failable Initializer

다른 이니셜라이저처럼 서브클래스에서 슈퍼클래스의 failable 이니셜라이저를 오버라이드할 수 있습니다.

또한, 서브클래스의 nonfailable 이니셜라이저가 슈퍼클래스의 failable 이니셜라이저를 오버라이드할 수도 있습니다.

이는 비록 슈퍼클래스의 초기화가 실패할 수 있더라도,

서브클래스에서의 초기화 실패는 일어날 수 없는 서브클래스를 정의할 수 있다는 의미입니다.

(반대로 failable 이니셜라이저가 nonfailable 이니셜라이저를 오버라이드하는 것은 불가능합니다.)

 

아래 예제 코드는 Document라는 클래스를 정의하고 있습니다.

이 클래스는 초기화할 때 name 값으로 non-empty 문자열이나 nil의 값을 가질 수 있고,

빈 문자열은 될 수 없습니다.

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
    }
}

 

다음 예제 코드는 Document의 서브클래스인 AutomaticallyNamedDocument를 정의합니다.

이 서브클래스는 Document의 designated 이니셜라이저 두 개를 모두 오버라이드합니다.

따라서 만약 이 서브클래스의 인스턴스가 name 없이 초기화되거나 init(name:)에 빈 문자열이 전달되면

name의 초기값이 "[Untitled]"인 인스턴스가 생성됩니다.

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
        }
    }
}

AutomaticallyNamedDocument는 슈퍼클래스의 failable 이니셜라이저인 init?(name:)을

nonfailalbe인 init(name:) 이니셜라이저가 ovveride하고 있습니다.

AutomaticallyNamedDocument는 빈 문자열 케이스를 슈퍼클래스와 다른 방법으로 처리하고 있기 때문에,

서브클래스의 이니셜라이저는 실패하지 않습니다.

 

이니셜라이저에서 슈퍼클래스의 failable 이니셜라이저를 서브클래스의 nonfailable 이니셜라이저에서 호출하여

foced unwrapping을 사용할 수 있습니다.

예를 들어, 아래의 UntitledDocument 서브클래스는 항상 "[Untitled]"로 명명되고,

초기화동안 슈퍼클래스의 failable init(name:) 이니셜라이저를 사용합니다.

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

위 코드에서는 UntitleDocument 이니셜라이저에서 Document의 기본 이니셜라이저를 오버라이드했고,

그 내부에서 슈퍼클래스의 failable 이니셜라이저를 호출하는데

그 값이 옵셔널 값을 갖지 않도록 느낌표(!)를 사용해 강제 언래핑 했습니다.

 

The init! Failable Initializer

init 키워드 뒤에 느낌표를 붙여 적절한 타입의 언래핑한 옵셔널 인스턴스를 생성하는

failable 이니셜라이저를 정의할 수 있습니다.

 

init?으로부터 init!으로 delegate 할 수 있고, 그 반대도 가능합니다.

그리고 init!으로 init?을 오버라이드할 수 있고, 그 반대도 가능합니다.

또한 init! 이니셜라이저가 초기화 실패한다면 assertion을 트리거하지만,

init으로부터 init!으로 delegate 할 수 있습니다.

 

Required Initializers

클래스의 서브 클래스가 해당 이니셜라이저를 필수로 구현하도록 하려면

required 수식어를 클래스 이니셜라이저 앞에 붙여줍니다.

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}

이러한 required 이니셜라이저를 상속 받은 서브클래스에서도 반드시 required를 추가하여

다른 서브클래스에게도 이 이니셜라이저는 required 이니셜라이저라는 것을 알려줘야 합니다.

class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

 

Setting a Default Property Value with a Closure or Function

만약 저장 프로퍼티의 기본값이 커스터마이징 또는 설정이 필요하면, 

그 속성의 커스터마이즈 기본값을 제공하는 클로저 또는 전역 함수를 사용할 수 있습니다.

 

이러한 클로저나 함수는 일반적으로 프로퍼티와 동일한 타입의 임시 변수를 생성하고,

원하는 초기 상태를 갖도록 한 뒤 그 임시 값을 반환합니다.

 

다음 코드는 클로저를 사용하여 기본값을 제공하는 방법을 보여줍니다.

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
    }()
}

someProperty는 클로저가 실행된 후 반환 타입이 someType인

someValue를 기본값으로 갖게 됩니다.

프로퍼티를 초기화할 때 클로저를 사용한다면,
인스턴스의 다른 프로퍼티들은 클로저가 수행되는 시점에서 초기화가 완료된 것이 아닙니다.
따라서, 클로저 내에서 다른 프로퍼티의 값에 접근할 수 없습니다.
물론 self와 인스턴스 메서드도 호출할 수 없습니다.

 

아래 예제 코드는 체스 게임의 보드를 모델링하는 Chessboard 구조체를 정의합니다.

8x8 보드이며 검정색, 하얀색 사각형이 번갈아 나옵니다.

boardColor 배열은 클로저를 통해 초기화 됩니다.

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]
    }
}

 

새로운 Chessboard 인스턴스가 생성될 때, 클로저가 실행되고 boardColors의 기본값이 반환됩니다.

위 예제에서의 클로저는 보드의 각 사각형에 적절한 색을 설정하여

임시 배열인 temporaryBoard에 저장합니다.

setup이 완료되면 이 값을 반환합니다.

반환된 값은 boardColors에 저장됩니다.

 

 

참고

https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

 

 

반응형