티스토리 뷰

728x90

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

 

초기화 과정중 실패할 가능성이 있는 초기자를 init? 키워드를 사용해 표시할 수 있습니다.

 

NOTE

실패가능 초기자는 반환값으로 옵셔널 값을 생성합니다.
초기화에 실패하는 부분에서 return nil 을 작성해 초기화가 실패했다는걸 나타내줍니다.

비록 초기화가 실패했을때 return nil 을 써주지만, 성공했을 경우엔 return 키워드를 사용하지 않습니다.

 

아래는 실패 가능 초기자 Int(exactly:)를 사용한 예제 입니다.

알고가자

Int(exactly:)
- 소수점 값이 0이면 정수만 추출하고, 소수점 값이 있으면 nil을 출력합니다.
 ex)
Int(exactly: 2.5) -> nil
Int(exactly: 2.0) -> 2​
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"

 

다음은 초기자에 입력값이 없으면 초기화 실패가 발생하도록 구현한 예제입니다.

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

// 초기값이 있을때
let someCreature = Animal(species: "Giraffe")

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// Print: An animal was initialized with a species of Giraffe

// 초기값이 없을때 
let noCreature = Animal(species: "")

if noCreature == nil {
    print("The no creature could not be initialized")
}
// Print: The no creature could not be initialized

 

 

열거형에 사용하는 실패 가능한 초기자
(Failable Initializers for Enumerations)

 

열거형에서도 실패 가능한 초기자를 사용할 수 있습니다.

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

 

위 예제에서 초기화시 지정된 특정 온도 표시단위 ("K", "C", "F")가 아닌 경우 초기화 결과로 nil을 반환 합니다.

 

아래를 확인해보시죠.

let fahrenheitUnit = TemperatureUnit(symbol: "F")

if fahrenheitUnit != nil {
    print("This is a defined temperature unit, 초기화 성공.")
}
// Print: This is a defined temperature unit, 초기화 성공.

// TemperatureUnit symbol에는 "X" 가 없으니 nil을 반환 합니다.
let unknownUnit = TemperatureUnit(symbol: "X")

if unknownUnit == nil {
    print("This is not a defined temperature unit, 초기화 실패.")
}
// Print: This is not a defined temperature unit, 초기화 실패.

 

 

열거형에서 Raw 값을 사용하는 실패 가능한 초기자
(Failable Initializers for Enumerations with Raw Values)

 

enum이 가지고 있는 기능중 rawValue를 초기자 인자로 넣어 초기화에 사용할 수 있습니다.

아래와 같이 기능은 같지만 더 간결하게 사용할 수 있습니다. (switch문 필요x)

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, 초기화 성공.")
}
// Prints "This is a defined temperature unit, 초기화 성공."

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, 초기화 실패.")
}
// Prints "This is not a defined temperature unit, 초기화 실패."

 

초기자 실패의 생성 (Propagation of Initialization Failure)

 

실패 가능 초기자에서 실패가 발생하면 즉시 관련된 초기자가 중단 됩니다.

 

아래 예제를 보겠습니다.

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

// 초기화 성공 (name 값도 있고, quantity도 1 보다 큼으로)
if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Print: Item: sock, quantity: 2


// 초기화 실패 (name 값은 있지만, quantity는 1보다 작으므로)
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// Print: Unable to initialize zero shirts


// 초기화 실패 (quantity는 1보다 크지만, name 값이 없으므로)
if let noNameProduct = CartItem(name: "", quantity: 5) {
    print("Item: \(noNameProduct.name), quantity: \(noNameProduct.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// Print: Unable to initialize zero shirts

 

 

실패 가능한 초기자의 오버라이딩 (Overriding a Failable Initializer)

 

아래처럼 초기자를 오버라이딩 할 수 있습니다.

      상위클래스                         하위클래스 
실패가능한 초기자  --->  실패불가능한 초기자

                                     변환

 

반대로는 불가능 합니다.

 

 

아래 Document 클래스는 초기화 할때, name값으로 특정 String을 지정하거나, nil을 지정할 수 있습니다.

name 값이 비어있는 (empty)의 경우엔 초기화 실패를 나타내는 nil을 반환합니다.

class Document {
    var name: String?
    
    init() {}
    
    // 실패가능 초기자
    init?(name: String) { 
        if name.isEmpty { return nil }
        self.name = name
    }
}

 

Document의 하위클래스인 AutomaticallyNamedDocument 클래스에서는 기본 초기자와, 지정초기자를 override 해서 실패가능 초기자를 실패불가능 초기자로 만들었습니다.

초기값이 없는 경우엔 name에 기본값으로 Unnamed를 넣도록 했습니다. 

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()    
        self.name = "Unnamed"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "Unnamed"
        } else {
            self.name = name
        }
    }
}

 

Document의 또다른 하위클래스 UnNamedDocument에서는 기본 초기자를 override 하여 초기자를 구현했고, 

강제 언래핑 (! 느낌표)을 사용하여 해당 값이 옵셔널 값을 갖지 않도록 하였습니다. 
super.init(name: "Unnamed")!

class UnnamedDocument: Document {
    override init() {
        super.init(name: "Unnamed")!
    }
}

 

실패 가능한 init! 초기자 (The init! Failable Initializer)

 

실패 가능한 초기자 init? 을 init!로 오버라이딩 하거나, 위임하여 사용할 수 있습니다.

 

 

필수 초기자 (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
    }
}

 

NOTE

필수 초기자 표시를 해도 꼭 구현할 필요는 없습니다.

 

 

클로저나 함수를 이용해 기본 프로퍼티 값을 설정하기
(Setting a Default Property Value with a Closure or Function)

 

기본값 설정이 다소 복잡한 계산을 필요로 한다면, 클로저나 함수를 이용해 값을 초기화 하는데 이용할 수 있습니다.

기본값 지정을 위해 클로저를 사용하는 형태의 코드는 아래와 같습니다.

 

import UIKit

class SomeClass {
    let testLabel: UILabel = {
        let label = UILabel()
        label.text = "여러 기본 값을 한꺼번에 할당할 수 있습니다."
        label.textColor = .brown
        label.font = .systemFont(ofSize: 30)
        return label
    }()  // <- 초기화
}

 

 

testLabel은 클로저가 실행된 후 반환 타입이 UILabellabel을 기본 값으로 갖게 됩니다.

 


초기화 내용이 정말 많습니다..

 

기껏해야 Model 만들때 init() 해봤던게 전부였는데,

추후에 작업해보면서 오늘 배운것들도 사용해봐야겠습니다. 

728x90