본문 바로가기

Swift

Initialization - 무엇인가

728x90

공식문서를 읽고 번역한 것입니다. 

 

초기화는 클래스, 구조체, 열거형 인스턴스를 사용하기 위해서 준비 작업을 하는 단계입니다. 이 단계에서는 각 저장 프로퍼티의 초기 값을 설정합니다.

초기화 과정은 initializer를 정의하는 것으로 구현할 수 있습니다. 초기화와 반대로 여러 값, 자원의 해지를 위해서 deinitializer를 사용할 수 있습니다.

(Swift의 initializer는 값을 반환하지 않습니다. )


저장 프로퍼티를 위한 초기값 설정

인스턴스의 저장 프로퍼티의 경우, 사용하기 전에 반드시 초기 값을 설정해야 합니다. 이 값을 기본 값으로 설정할 수 있고 특정 값으로 설정할 수 있습니다. initializer에서 저장 프로퍼티에 값을 직접 설정하면 프로퍼티 옵저버가 호출되지 않고 값 할당이 수행됩니다. 

 

initializer

initializer는 특정 타입의 인스턴스를 생성합니다. initializer의 가장 간단한 형태는 파라미터가 없고 init 키워드를 사용하는 것입니다.

init() {
	// 초기화 과정을 수행합니다. 
}

 

아래의 예제는 화씨 온도 구조체를 만들어서 온도라는 프로퍼티를 선언하고 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"

 

기본 프로퍼티

프로퍼티의 선언과 동시에 값을 할당하면 초기 값으로 사용할 수 있습니다.

 

✅ 항상 같은 초기 값을 갖는다면, 기본 프로퍼티를 사용하는 것이 좋습니다. 이 기본값은 상속에도 적용됩니다. (상속 시 함께 상속됩니다.)

✅ 프로퍼티에 타입을 선언하지 않아도 컴파일러는 초기 값을 참조해서 타입을 추론합니다.

 

struct Fahrenheit {
    var temperature = 32.0
}

위의 코드는 프로퍼티 선언과 동시에 초기 값을 할당한 것입니다. 


커스터마이징 초기화

초기화 프로세스를 입력 값과 옵셔널 프로퍼티 타입 or 상수 값을 할당해서 커스터마이징을 할 수 있습니다. 

 

초기화 파라미터

초기화 정의에 파라미터를 정의해서 사용할 수 있습니다. 

 

아래의 예제는 temperatureInCelsius 프로퍼티를 초기화 파라미터로 입력받아 초기화에 사용하는 것입니다. 

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가 자동으로 하나를 할당해서 제공합니다.

 

아래의 코드는 초기화 과정에서 파라미터를 3개 입력받는 이니셜라이저와 하나만 입력받는 이니셜라이저의 예제입니다.

struct Color {
    let red, green, blue: Double
    
    // 파라미터 3개
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    
    // 파라미터 1개
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

 

Color 인스턴스를 생성할 때, 인자값 3개 또는 1개를 이용해서 생성할 수 있습니다. 만약 인자 레이블을 포함하지 않으면 컴파일 에러가 발생합니다.

// 인자값을 사용해서 인스턴스 생성 
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)

 

인자 레이블이 없는 이니셜라이저 파라미터 

코드를 작성할 때 인자 레이블을 생략하는 것이 더 명료할 경우, _기호를 사용하여 이니셜라이저에서 인자 레이블을 생략할 수 있습니다.

 

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

// 인자 레이블 없이 Celsius 인스턴스 초기화 가능 
let bodyTemperature = Celsius(37.0)

 

 

 

옵셔널 프로퍼티 타입

프로퍼티의 최초 값이 없고 나중에 지정할 값을 옵셔널로 선언해서 사용할 수 있습니다. 옵셔널 프로퍼티는 자동으로 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."

 

초기화 중에 상수 프로퍼티 할당

이니셜라이저에서는 상수 프로퍼티에 값을 할당하는 것도 가능합니다.

🔥 주의할 점은, 클래스 인스턴스에서 상수 프로퍼티는 초기화 중 그 클래스 안에서만 변경이 가능하고 서브 클래스에서는 불가합니다.

 

*상수 : 프로퍼티를 let으로 선언해서 해당 프로퍼티는 처음 초기화 이후 변경되지 않는 프로퍼티임을 표현할 수 있습니다.

class SurveyQuestion {
	// text의 경우 let으로 지정 -> 초기화 이후 변경 불가 
    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?"

기본 이니셜라이저 

모든 프로퍼티의 초기값이 설정되어 있고 하나의 초기자도 정의하지 않았다면, Swift는 모든 프로퍼티를 기본 값으로 초기화하는 기본 초기자를 제공합니다.

 

아래의 코드는 이니셜라이저를 정의하지 않았지만,, Swift가 기본적으로 제공하는 ShoppingListItem()을 통해 사용할 수 있음을 보여줍니다.

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

var item = ShoppingListItem()

 

구조체 타입을 위한 Memberwise Initializer

기본 이니셜라이저와 다르게 memberwise 이니셜라이저는 프로퍼티가 기본 값이 없어도 커스텀 이니셜라이저를 정의하지 않았다면, memberwise 이니셜라이저를 제공해줍니다. 

 

이 초기자는 선언한 모든 프로퍼티를 인자로 사용합니다. ⬇️

struct Size {
    var width = 0.0, height = 0.0
}

let twoByTwo = Size(width: 2.0, height: 2.0)

값 타입을 위한 이니셜라이저 위임

이니셜라이저에서 다른 이니셜라이저를 호출할 수 있습니다. 이 과정을 이니셜라이저 위임이라고 합니다.

 

값 타입과 클래스 타입 간 이니셜라이저 위임 동작이 다릅니다.

✔️ 값 타입(structures, enumerations)은 상속을 지원하지 않아 이니셜라이저를 자기 자신의 다른 이니셜라이저에만 사용할 수 있습니다.

✔️ 클래스 타입(class)은 상속이 가능하기 때문에 superclass의 이니셜라이저를 subclass에서 호출할 수 있습니다. 

 

상속받은 모든 프로퍼티의 초기화에 대한 책임은 이니셜라이저에 있습니다. 이러한 제약이 이니셜라이저의 복잡성을 줄여주고 이니셜라이저가 의도와 다르게 사용되는 것을 막아줍니다.

 

🤔 커스텀 이니셜라이저를 사용하면서 기본 이니셜라이저와 memberwise 이니셜라이저도 사용하고 싶다면, 커스텀 이니셜라이저를 오리지널 클래스에서 구현하지 않고 extension에서 구현하면 됩니다.

 

struct Size {
    var width = 0.0, height = 0.0
}

struct Point {
    var x = 0.0, y = 0.0
}

위와 같이 Size와 Point라는 값 타입의 구조체를 선언하겠습니다. ⬆️

 

그리고 해당 구조체를 아래와 같이 다른 구조체에서 프로퍼티로 사용할 수 있습니다. ⬇️

struct Rect {
	
    // 구조체를 프로퍼티로 사용 
    var origin = Point()
    var size = Size()
    
    // 초기화 방법 1
    init() {}
    
    // 초기화 방법 2
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    
    // 초기화 방법 3
    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)
    }
}

 

초기화 방법 1을 통해서 초기화를 하면 아래와 같이 각 프로퍼티가 기본 값을 초기 값으로 갖습니다. 

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

 

초기화 방법 2를 사용하면 초기화를 수행할 때 프로퍼티 값을 지정할 수 있습니다.

이 이니셜라이저는 memberwise 이니셜라이저와 동일합니다.

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)

 

초기화 방법 3에서는 내부에서 다른 초기자인 init(center: Point, size: Size)를 사용합니다. 그래서 특정 수행을 한 후 init(center: Point, size: Size)를 호출해 초기화를 이 이니셜라이저에게 위임합니다.

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)

 

✅ 이니셜라이저를 extension을 사용해서 선언하면, 첫 번째/두 번째 이니셜라이저는 자동으로 생성되고 extension에 선언한 이니셜라이저도 사용할 수 있습니다.

 

 

이어서 클래스 상속과 초기화 작성하겠습니다. 

'Swift' 카테고리의 다른 글

Initialization - 그 외  (0) 2022.04.20
Initialization - 상속과 초기화  (0) 2022.04.20
Hashable  (0) 2022.04.14
MVVM+RxSwift  (0) 2022.04.07
Type Casting  (0) 2022.04.01