본문 바로가기
Swift 공식 가이드/Swift 3

Initialization (1/3)

by 토끼찌짐 2017. 3. 12.

Apple 제공 Swift 프로그래밍 가이드(3.0.1)의 Initialization 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.



들어가며

초기화initialization는 클래스, 구조체, ENUM의 인스턴스를 사용하기 위한 준비 과정이다. 이 과정에서 새로운 인스턴스를 사용하기 위해 필요한 셋팅들을 한다. 모든 stored 프로퍼티에 초기값을 할당하는 것 등을 예로 들어볼 수 있다.

초기화를 위해 이니셜라이저initializer를 정의하자. Swift에서는 이니셜라이저가 따로 값을 반환하지 않는다. 따라서 Objective-C에서 많이 쓰던 self = [super init]; 은 Swift에서 쓸 수 없다.

이니셜라이저의 주목적은 새로운 인스턴스가 제대로 사용할 준비가 되었음을 보장하는 것이다.

클래스 타입의 인스턴스는 디이니셜라이저deinitializer도 만들 수 있다. 자세한 것은 디이니셜라이저를 배울 때 살펴보자.



Setting Initial Values for Stored Properties

클래스/구조체의 인스턴스는 생성될 때 반드시 모든 Stored 프로퍼티에 값이 셋팅되어야 한다. 따라서 정의 부분에 디폴트값이 없는 Stored 프로퍼티는 반드시 이니셜라이저 안에서 초기값 셋팅이 되어야 한다. 

<note> 이때 프로퍼티 옵저버는 불리지 않는다.


<Initializers & Default Property Values>

이니셜라이저를 호출하여 새로운 인스턴스를 생성할 수 있다. 이때 정의부분에서 디폴트값이 정의되지 않은 Stored 프로퍼티들은 모두 이니셜라이저 안에서 초기값 셋팅이 되어야 한다.

init() {
    // 패러미터가 없는 인스턴스 메서드와 같은 모양새다
    // 이 안에서 초기작업들을 수행
}
 // 이니셜라이저 안에서 초기값을 Set 하거나
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}

var f = Fahrenheit()
print("디폴트 온도는 \(f.temperature)도") //32.0도



// 디폴트 값을 미리 정의해야 한다
struct Fahrenheit {
    var temperature: Double = 32.0
}

<note> 매번 초기값이 똑같은 프로퍼티라면 정의부분에서 디폴트 값을 주는 것이 더 좋다. 명확하고 짧은 이니셜라이저를 만들 수 있으며 프로퍼티의 타입을 유추하기 쉬워지기 때문이다. 또한 디폴트 이니셜라이저와 이니셜라이저 상속에도 유리해진다. (뒤에서 설명)



Customizing Initialization

초기화 과정을 커스터마이징할 수 있다. 이와 관련해서 다음과 같은 것들을 살펴보겠다 : 인풋 패러미터, 옵셔널 프로퍼티 타입, 초기화 과정에서 상수 프로퍼티에 값 할당하기


<초기화 패러미터Initialization Parameters>

이니셜라이저를 정의할 때, 이니셜라이저가 받을 패러미터를 다음처럼 정의할 수 있다.

struct Celsius {
    var temperatureInCelsius: Double
    
    // 패러미터를 정의하는 방법은 메서드와 동일하다
    // argument label과 parameter name을 분리하여 정의 가능
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}

let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
let freezingPointOfWater = Celsius(fromKelvin: 273.15)



<Parameter Names and Argument Labels>

이니셜라이저의 패러미터는 함수/메서드의 패러미터와 마찬가지로 Parameter name과 Argument label을 가진다. 직접 Argument label을 정의할 수 있으며 만약 정의하지 않을 경우 자동으로 만들어진다. (이니셜라이저의 경우 init 외의 이름을 가질 수 없기 때문에 패러미터의 이름과 타입이 이니셜라이저를 구분하기 위한 중요한 역할을 하기 때문이다.)

struct Color {
    let red, green, blue: Double
    
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    
    // white의 argument label을 명시하지 않아도
    // 이 패러미터의 argument label은 자동으로 white가 됨
    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 label을 명시하지 않았기 때문
let veryGreen = Color(0.0, 1.0, 0.0)



<Initializer Parameters Without Argument Labels>

이니셜라이저를 호출할 때 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>

만약 초기화 과정에서 "아직 값이 없음" 상태가 될 수 있는 Stored 프로퍼티가 필요한 경우에는 옵셔널 타입의 프로퍼티를 정의하도록 하자. 이런 프로퍼티들은 초기화 과정에서 "아직은 값이 없음"의 의미로써 자동으로 nil이 할당된다.

class SurveyQuestion {
    var text: String
    var response: String? // 설문조사의 답은 미리 알 수가 없다
    
    init(text: String) {
        self.text = text
        // response 프로퍼티는 자동으로 nil로 셋팅이 된다
    }
    
    func ask() {
        print(text)
    }
}

let cheeseQuestion = SurveyQuestion(text: "Do you like this?")
// 이 시점에서 cheeseQuestion.response 는 nil
cheeseQuestion.ask()
cheeseQuestion.response = "YES"



<초기화 과정에서 상수 프로퍼티에 값 할당하기Assigning Constant Properties During Initialization>

초기화 과정에서 상수 프로퍼티에 초기값을 셋팅할 수 있다. (주: 상수 프로퍼티는 한 번 값이 할당되면 그 다음부터는 변경할 수가 없다. 따라서 이 말은 상수 프로퍼티를 정의할 때 디폴트값을 셋팅하지 않은 경우에만 해당하는 말이다.)

class SurveyQuestion {
    let text: String // 디폴트값이 없기 때문에(=아직 값이 할당되지 않아서)
    var response: String?
    
    init(text: String) {
        self.text = text // 초기화 때 상수 프로퍼티에 값을 할당가능
    }
    
    func ask() {
        print(text)
    }
}

let cheeseQuestion = SurveyQuestion(text: "How about this?")

<note> 클래스 인스턴스의 상수 프로퍼티는 그 클래스의 이니셜라이저에서만 값 할당이 가능하다. 서브클래스에서 그 상수 프로퍼티의 값을 건드는 것은 불가능하다.



Default Initializers

Swift는 구조체/클래스가 다음 사항들을 모두 만족할 경우 디폴트 이니셜라이저를 제공한다. (1) 모든 프로퍼티에 디폴트 값이 정의되어있고 (2) 이니셜라이저가 하나도 정의되어 있지 않을 때.

디폴트 이니셜라이저는 모든 프로퍼티가 디폴트값으로 셋팅된 인스턴스를 생성해준다.

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

var item = ShoppingListItem()


그런데 따로 이니셜라이저를 정의하면(=커스텀 이니셜라이저를 제공하면), 디폴트 이니셜라이저가 제공되지 않는 이유 무엇일까?

-> 커스텀 이니셜라이저를 정의한 이상, 그것을 통해서만 완전한 초기셋팅이 가능할지도 모른다. 따라서 실수로 디폴트 이니셜라이저로 인스턴스를 만들어서 불완전한 초기셋팅이 될지도 모르는 상황을 미리 방지해주는 것이다. 만약 커스텀 이니셜라이저와 디폴트 이니셜라이저를 둘 다 쓰고 싶은 경우는 확장Extension을 통해 커스텀 이니셜라이저를 구현하는 것을 추천한다.



<구조체에서 쓰이는 멤버단위 이니셜라이저Memberwise Initializers for Structure Types>

구조체에 따로 이니셜라이저가 정의되지 않았다면 자동으로 Memberwise initializer가 제공된다. Stored 프로퍼티에 디폴트값이 없더라도 제공된다.

struct Size {
    var width = 0.0
    var height: Double
}
let twoByTwo = Size(width: 2.0, height: 2.0)
// Memberwise initializer : 모든 프로퍼티에 값을 할당하는 형태이다




Initializer Delegation for Value Types

이니셜라이저 딜리게이션initializer delegation이란 이니셜라이저 안에서 다른 이니셜라이저를 호출하는 것을 말한다. 이것을 통해 이니셜라이저가 여러 개일때 코드의 중복을 줄일 수 있다.

이니셜라이저 딜리게이션의 룰은 사용할 타입이 value 타입인지 reference 타입인지에 따라 달라진다. value 타입은 단순히 다른 이니셜라이저가 제공하는 것들을 대리수행delegation만 하면 된다. 그러나 refrence 타입, 즉 클래스 같은 타입들은 상속이 가능하기 때문에 상위 클래스의 초기셋팅까지도 잘 되었는지를 보장할 필요가 있으므로 까다로워지는 것이다. 일단 여기서는 value 타입만 살펴보자.

value 타입의 경우에는 커스텀 이니셜라이저 안에서 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()
    
    // 3개의 이니셜라이저를 정의하고 있다
    
    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)
        // 다른 이니셜라이저를 호출해주고 있다
        // 참고로 self.init 호출은 이니셜라이저에서만 가능하다
    }
}

// (1) 디폴트 이니셜라이저와 같은 동작
let basicRect = Rect()

// (2) memberwise 이니셜라이저와 같은 동작
let originPoint = Point()
let rectSize = Size(width: 5.0, height: 5.0)
let originRect = Rect(origin: originPoint, size: rectSize)

// (3) 좀 더 복잡한 이니셜라이저를 사용
let centerPoint = Point(x: 2.5, y: 2.5)
let centerRect = Rect(center: centerPoint, size: rectSize)


이처럼 value 타입의 이니셜라이저 딜리게이션은 간단하다. 그러나 클래스 타입 (reference type)에 대해서는 여러가지 살펴볼 사항들이 있다. 다음 시간에는 클래스의 상속과 초기화에 대해 중점적으로 다뤄보도록 하겠다.


 2편을 작성하였습니다 > http://wlaxhrl.tistory.com/48

'Swift 공식 가이드 > Swift 3' 카테고리의 다른 글

Initialization (3/3)  (0) 2017.03.13
Initialization (2/3)  (0) 2017.03.13
Inheritance  (0) 2017.03.01
Subscripts  (0) 2017.03.01
Methods  (0) 2017.03.01