Swift Language

Swift Language - 13 - 초기화 (Initialization)

xnoag 2023. 2. 2. 16:26

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

 

 

이니셜라이저 (Initializers)

// 가장 간단한 형태는 파라미터가 없고 init 키워드를 사용하는 것입니다.
init() {
    // perform some initialization here
}


// 화씨 온도 구조체를 만들어 온도라는 프로퍼티를 선언하고 이니셜라이저에서 초기화하는 코드입니다.
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)

// 프로퍼티의 선언과 동시에 값을 할당하면 그 값을 초기 값으로 사용할 수 있습니다.
// 항상 같은 초기 값을 갖는 다면 기본 프로퍼티를 사용하는 것이 좋습니다.
struct Fahrenheit {
    var temperature = 32.0
}

 

초기화 파라미터 (Initialization Parameters)

// 초기화 정의에 파라미터를 정의해 사용할 수 있습니다. 
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

 

파라미터 이름과 인자 레이블 (Parameter Names and Argument Labels)

// 이니셜라이저는 특정 메소드에서 지정하는 메소드 이름을 지정하지 않고 이니셜라이저 식별자로 파라미터를 사용합니다. 
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 인스턴스를 인자값 3개 혹은 하나를 이용해 생성할 수 있습니다.
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)


// 인자 레이블이 없는 이니셜라이저 파라미터 (Initializer Parameters Without Argument Labels)
// 코드를 작성할 때 인자 레이블을 생략하는 것이 더 명료한 경우 _ 기호를 사용해 이니셜라이저에서 인자 레이블을 생략할 수 있습니다.
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


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

 

기본 이니셜라이저 (Default Initializers)

/* 만약 모든 프로퍼티의 초기값이 설정돼 있고, 
하나의 초기자도 정의하지 않았다면 Swift는 모든 프로퍼티를 기본 값으로 초기화 화는 기본 초기자를 제공해 줍니다. */

/* 아래 코드는 이니셜라이저를 정의하지 않았지만 
Swift가 제공하는 기본 이니셜라이저 ShoppingListItem()를 사용할 수 있음을 보여주는 예제입니다. */
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

 

 

구조체 타입을 위한 맴버쪽 이니셜라이저 (Memberwise Initializers for Structure Types)

/* 기본 이니셜라이저와 다르게 맴버쪽 이니셜라이저는 프로퍼티가 기본 값이 없어도 
커스텀 이니셜라이저를 정의하지 않았다면 멤버쪽 이니셜라이저를 제공해 줍니다. */
struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

값 타입을 위한 이니셜라이저 위임 (Initializer Delegation for Value Types)

/* 이니셜라이저에서 다른 이니셜라이저를 호출할 수 있습니다. 
이 과정을 이니셜라이저 위임이라 합니다. 
값 타입(structures, enumerations)과 클래스 타입(class)간 이니셜라이저 위임 동작이 다릅니다. 
값 타입은 상속을 지원하지 않아 이니셜라이저를 자기 자신의 다른 이니셜라이저에만 사용할 수 있습니다. 
반면 클래스 타입은 상속이 가능하기 때문에 superclass의 이니셜라이저를 subclass에서 호출 가능합니다. */

// 다음 예제는 Size와 Point라는 값 타입 구조체를 선언한 코드입니다.
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() {}
    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)
    }
}


// 첫 이니셜라이저 init()를 이용해 초기화를 하면 아래와 같이 각 프로퍼티가 기본 값을 초기 값으로 갖게 됩니다.
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)


// 두번째 이니셜라이저 init(origin: Point, size: 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)


// 세번째 이니셜라이저 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)

 

 

클래스 상속과 초기화 (Class Inheritance and Initialization)

모든 클래스의 저장 프로퍼티와 superclass로부터 상속받은 모든 프로퍼티는 초기화 단계에서 반드시 초기값이 할당돼야 합니다. Swift에서는 클래스 타입에서 모든 프로퍼티가 초기 값을 갖는 것을 보장하기 위해 2가지 방법을 지원합니다.

 

지정 초기자와 편리한 초기자 (Designated Initializers and Convenience Initializers)

지정 초기자는 클래스의 주(primary) 초기자입니다. 지정 초기자는 클래스의 모든 프로퍼티를 초기화 합니다. 클래스 타입은 반드시 한개 이상의 지정 초기자가 있어야 합니다. 편리한 초기자는 초기화 단계에서 미리 지정된 값을 사용해서 최소한의 입력으로 초기화를 할 수 있도록 해주는 초기자입니다. 편리한 초기자 내에서 반드시 지정 초기자가 호출돼야 합니다.

 

지정 초기자와 편리한 초기자의 문법 (Syntax for Designated and Convenience Initializers)

// 클래스의 지정 초기자의 문법은 값 타입 초기자와 같습니다.
init(parameters) {
    statements
}

// 편리한 초기자는 기본 초기자와 문법이 같지만 init 앞에 convenience 키워드를 붙여 줍니다.
convenience init(parameters) {
    statements
}

 

니셜라이저의 상속과 오버라이딩 (Initializer Inheritance and Overriding)

/* Swift에서는 기본적으로 subclass에서 superclass의 이니셜라이저를 상속하지 않습니다. 
이유는 superclass의 이니셜라이저가 무분별하게 상속돼 복잡하게 돼 
subclass에서 이것들이 잘못 초기화 되는 것을 막기 위함입니다. */

// superclass의 초기자를 오버라이드 하기 위해서는 subclass에서 그 초기자에 override 키워드를 붙이고 재정의 합니다.
class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)


// subclass에서 superclass의 초기자를 오버라이드 합니다.
class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}


// 인스턴스 생성 후 초기 값이 변한 것을 확인할 수 있습니다.
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)

 

자동 초기자 인스턴스 (Automatic Initializer Inheritance)

서브클래스는 수퍼클래스의 초기자를 기본적으로 상속하지 않습니다. 하지만 특정 상황에서 자동으로 상속 받습니다. 사실 많은 상황에서 직접 초기자를 오버라이드 할 필요가 없습니다. 서브클래스에서 새로 추가한 모든 프로퍼티에 기본 값을 제공하면 다음 두가지 규칙이 적용됩니다. 규칙1 서브클래스가 지정초기자를 정의하지 않으면 자동으로 수퍼클래스의 모든 지정초기자를 상속합니다. 규칙2 서브클래스가 수퍼클래스의 지정초기자를 모두 구현한 경우(규칙 1의 경우 혹은 모든 지정초기자를 구현한 경우) 자동으로 수퍼클래스의 편리한 초기자를 추가합니다.

 

// 다음은 지정초기자, 편리한 초기자 그리고 자동 초기자의 상속의 예제 입니다.
class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}


// 예제는 지정초기자를 이용해 Food 인스턴스의 name의 초기값을 “Bacon”으로 설정해 생성하는 예제입니다.
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"


// 다음 예제는 편리한 초기자를 이용해 인스턴스를 생성한 코드입니다.
// mysteryMeat은 초기값을 지정하지 않았음으로 편리한 초기자에 의해 "[Unnamed]”를 갖습니다.
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"


// 다음 코드는 Food 클래스를 서브클래싱해서 생성한 RecipeIngredient 클래스에서 수퍼클래스의 편리한 초기자를 오버라이딩해서 사용한 예제입니다.
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)
    }
}

 

 

실패 가능한 초기자 (Failable Initializers)

// 초기화 과정 중에 실패할 가능성이 있는 초기자를 init뒤에 물음표(?)를 사용해 실패가 가능한 초기자라고 표시할 수 있습니다.
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 does not maintain value")
}
// Prints "3.14159 conversion to Int does not maintain value"


// 다음 코드는 초기자에 입력값이 없으면 초기화 실패가 발생하도록 구현한 예제입니다.
struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}


// 위에서 만든 Animal 구조체를 이용해 인스턴스를 생성하는데 동물의 종류를 Giraffe로 지정해 생성한 예입니다.
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"


// Animal 생성시 인자를 넣지 않고 생성한 경우 초기화에 실패해서 생성된 인스턴스는 nil을 갖게 되는 것을 확인할 수 있습니다.
let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"


// 열거형에서도 실패 가능한 초기자를 사용할 수 있습니다. 예제는 다음과 같습니다.
// 아래 예제에서는 초기화시 지정된 특정 온도 표시 단위가 아닌 경우 초기화 결과로 nil을 반환합니다.
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
        }
    }
}


/* TemperatureUnit(symbol: "F")에서 F는 TemperatureUnit 열거형에서 사전에 정의돼 있는 단위여서 
초기화가 정상적으로 수행됩니다. 
반면 TemperatureUnit(symbol: "X")에서 X는 정의되지 않은 단위여서 초기화에 실패합니다. */
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 is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

 

필수 초기자 (Required Initializers)

// 모든 서브클래스에서 반드시 구현해야 하는 초기자에는 아래 예제와 같이 required 키워드를 붙여 줍니다.
class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}


// 필수초기자를 상속받은 서브클래스에서도 반드시 required 키워드를 붙여서 다른 서브클래스에게도 이 초기자는 필수 초기자라는 것을 알려야 합니다.
class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

 

 

출처 https://jusung.gitbook.io/the-swift-language-guide/language-guide/14-initialization