Failable Initializers
: Class, Structure, Enumeration을 init할 때 initialization 과정이 실패할 가능성이 있는 경우 Failable Initializer를 정의할 수 있다. 'init' keyword 뒤에 '?'을 붙여서 정의한다.

* 똑같은 파라미터 타입, 갯수, 이름으로 failable initializer, nonfailable initializer를 동시에 정의할 수는 없다.

failable initializer는 optional instance를 return한다. init이 실패했을 경우는 nil을 return 한다.

* 엄밀히 말하면 initializer는 값을 return하지 않는다. init과정이 성공했다고 해서 'return self' 를 하지 않듯이, initializer에 'return nil'이 의미하는것은 실제 nil을 return 한다는 것이 아니라 init에 실패했다라고 알려주는 역할에 가깝다고 보면된다.

먼저, Swift에서 제공하는 failable Initializer를 사용해 보자. numeric conversion을 하는데 있어 type conversion에 실패를 할 경우 nil을 return 하는 init(exactly:) initializer를 살펴볼 것이다.

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value")
}
// wholeNumber를 Int type으로 conversion 하는데 아무 문제가 없으므로 Int 인스턴스를 return 한다.

let valueChanged = Int(exactly: pi)

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// pi는 Int로 Conversion 할 수 없기 때문에, nil을 return 한다.

이제 직업 failable Initializer를 사용해 보겠다.

struct Animal {
    let species: String
    
    init?(species: String) {
        if species.isEmpty { return nil }
        
        self.species = species
    }
}
// init에 넘어온 species 파라미터가 empty string이면 init에 실패한다. 

let someCreature = Animal(species: "Giraffe")
// someCreature 는 Animal 타입이 아니라, 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"

let anonymousCreature = Animal(species: "")
// anonymousCreature 는 Animal 타입이 아니라, Animal? 타입이다.

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

Failable Initializers for Enumerations
: Enumeration의 Failable Initializer는 파라미터로 넘어온 값에 매칭되는 enum 값이 없을때 init을 Fail 시킨다.

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 is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

Failable Initializers for Enumerations with Raw Values
: raw Value를 갖는 Enumeration은 자동으로 failable initializer, init?(rawValue)을 갖는다.
rawValue와 맞는 case가 없으면 initialization이 실패한다. 

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 is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

Propagation of Initialization Failure
: class, structure, enumeration의 failure initializer는 다른 failure initializer로 delegation이 가능하다. subclass 또한 super class의 failure initializer로 delegation이 가능하다.

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil } 
        
        self.name = name
    }
}

class CarItem: Product {
    let quantity: Int
    
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        
        self.quantity = quantity
        
        super.init(name: name)
    }
}

if let twoSocks = CarItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
//Prints "Item: sock, quantity: 2"

if let zeroShirts = CarItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"
// quantity가 1보다 작기 때문에 quantity 체크하는 initializer에서 init 과정이 실패하고, 더이상의 init 작업은 진행되지 않는다.

if let oneUnnamed = CarItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize on unnamed product"

Overriding a Failable Initializer
: sub class에서 failable initializer도 다른 initializer 처럼 override가 가능하다. 또한 super class의 failable initializer를 sub class에서 non-failable initializer로 override가 가능하다. 단, 이 경우에는 super class의 failable initializer로 init 작업을 delegate-up 하기 위해서는 force-unwrap 과정이 필요하다.

*super class의 failable initializer를 non-failable initializer로 override 하는 경우는 보통 자식클래스에서는 init 과정이 실패할 가능성이 없을경우 이런식으로 구현하는데 왠만하면 이 방식을 사용하지 않는것이 좋다. 명시적으로 force unwrap 하는 것은 이유를 불문하고 위험한 행동이다.

class Document {
    var name: String?
    
    init() {}

    init?(name: String) {
        if name.isEmpty { return nil }
        
        self.name = name
    }
}

위 Document 클래스는 name property에 empty string값이 들어가는것을 허용하지 않는다.(nil은 가능)

class AutomaticallyNameDocument: Document {
    override init() {
        super.init()

        self.name = "[Untitled]"
    }
    
    override init(name: String) {
        super.init()
        
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

위 AutomaticallyNameDocument 클래스에서는 initialization이 실패할 가능성을 차단했다. 

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

위 UntitledDocument 클래스는 name에 nil이 할당된 채로 instance가 초기화 되는것을 차단했다.


Required Initializers

: 모든 subclass에게 특정 initializer의 구현을 강제화할 목적으로 사용하는 것이 required initializer이다. super class의 required initializer는 무조건 구현해야 한다.

class SomeClass {
    required init() {
        
    }
}

subclass 에서도 'required' 키워드를 붙여줘야 한다. 그래야 subclass의 subclass에서도 required initializer의 구현을 강제화할 수 있기 때문이다. required initializer를 override 할 때에는 'override' 키워드는 붙이지 않는다.

class SomeSubclass: SomeClass {
    required init() {

    }
}

*단, 자동으로 super class의 initializer들을 상속받게 되는 경우에는 명시적으로 super class의 required initializer를 구현하지 않아도 된다.(예를들어, sub class의 stored property 들이 default 값을 가지고 있거나 stored property가 없어서 custom initializer가 필요없는 경우에 실제로 custom initializer를 제공하지 않은 경우.)


Setting a Default Property Value with a Closure or Function 
: stored property에 default 값을 할당하기 위한 방법 중 하나로 closure 또는 global function을 사용할 수 있다. 해당 closure와 global function은 instance가 생성될 때 실행되어 property에 값을 할당한다. 당연히 이 closure, global function 안에서는 다른 property들에 대한 접근, self 및 다른 instance 메서드들에 대한 접근도 허용하지 않는다.

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

let board = Chessboard()

print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"

'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Deinitialization  (0) 2017.04.01
[Swift 4.0] Initialization(1)  (0) 2017.04.01
[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
Posted by 홍성곤
,