Introduction
: class type에만 적용되는 개념. class instance가 dealloc 되기 직전에 호출된다.


How Deinitialization Works
: Swift는 ARC를 사용하여 메모리 관리를 하기 때문에 일반적인 경우에는 수동으로 메모리 관리를 할 필요가 없다. 하지만 자신의 resource를 사용하는 경우 수동으로 clean up하는 동작이 필요할 때가 있다. 예를들어, 파일을 열고 쓰는 class를 사용할 경우 class가 dealloc 되는 시점에 해당 파일을 닫는 작업이 실행되어야 한다.
: 직접 deinitializer를 호출하는 것은 허용되지 않는다. super class의 deinitializer는 sub class에서 자동 상속 되고, sub class의 deinitializer 마지막에 super class의 deinitializer가 불린다. 


Deinitializers in Action
: 아래 코드를 보며 deinitializer가 어떤 역할을 하는지 살펴 보도록 하자.

class Bank {
    static var coinsInBank = 10_000
    
    static func distribute(coins numberOfCoinsRequested: Int) -> Int {
        let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
        
        coinsInBank -= numberOfCoinsToVend
        
        return numberOfCoinsToVend
    }

    static func receive(coins: Int) {
        coinsInBank += coins
    }
}

class Player {
    var coinsInPurse: Int

    init(coins: Int) {
        coinsInPurse = Bank.distribute(coins: coins)
    }
    
    func win(coins: Int) {
        coinsInPurse += Bank.distribute(coins: coins)
    }

    deinit {
        Bank.receive(coins: coinsInPurse)
    }
}

var playerOne: Player? = Player(coins: 100)

print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
//Prints "A new player has joined the game with 100 coins"

Print("There are now \(Bank.coinsInBank) coins left in the bank")
//Prints "There are now 9900 coins left in the bank"

playerOne!.win(coins: 2_000)

print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
//Prints "PlayerOne won 2000 coins & now has 2100 coins"
print("The bank now only has \(Bank.coinsInBank) coins left")
//Prints "The bank now only has 7900 coins left"

playerOne = nil

print("PlayerOne has left the game")
//Prints "PlayerOne has left the game"
print("The bank now has \(Bank.coinsInBank) coins")
//Prints "The bank now has 10000 coins"

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

[Swift 4.0] Initialization(2)  (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 홍성곤
,

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 홍성곤
,

Introduction
: Objective-C와 달리 Swift의 initializer는 값을 return 하지 않는다. 또한 Swift에는 initializer와 pair되는 deinitializer가 있다. deinitializer에서 instance가 dealloc 되기전에 해야되는 작업을 해야한다. 


Setting Initial Values for Stored Properties
: Class와 Structure는 stored property들을 자신의 instance가 만들어 질때까지 적절한 값으로 세팅되어 있어야 한다.
즉, initializer 안에서 property의 초기값을 세팅하거나, property 선언과 동시에 default property 값을 세팅해야 한다.

* 위에 설명된 바와 같이 initializer안에서 property의 값을 초기화 하거나, property 선언과 동시에 default 값을 세팅하면 property observer들이 호출되지 않는다.


Initializers

init () {
    // perform some initialization here
}
// 가장 심플한 형태의 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"


Default Property Values
: property 초기화를 선언과 동시에 할 수도 있다.

struct Fahrenheit {
    var temperature = 32.0
}


Customizing Initialization
Initialization Parameters
: initializer는 파라미터를 가질 수 있다.

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

이처럼 원하는대로 initializer를 만들 수 있으며 각 initializer는 파라미터들을 가질 수 있다.

Parameter Names and Argument Labels
: initializer는 function과 같이 parameter 이름과 argument label을 가질 수 있다. parameter 이름은 initializer 내에서 paramenter를 참조할 때 쓰이고, argument label은 initializer를 호출할 때 쓰인다. 
그러나 initializer는 함수와 달리 자체적으로 이름을 가질 수 없다. 그래서 initializer끼리의 구별은 parameter의 이름과 타입으로 initializer를 구별한다. 그래서 Swift는 자동으로 Initializer의 parameter 이름을 argument label로 지정해준다. 

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
    }

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

initializer를 호출할 때는 꼭 argument label을 명시해줘야 한다. 그렇지 않으면, compile error가 발생한다.

let veryGreen = Color(0.0, 1.0, 0.0)
// compile error 발생~

Initializer Parameters Without Argument Labels
: initializer의 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
: 만약 당신이 만든 custom type이 optional type의 stored property를 갖고 있다면, 이런 의도일 것이다. "이 property는 initialization 동안에는 값을 가질수 없는 property 야" 또는 "이 property는 값을 가지고 있다가도 나중에는 값을 가질 수 없는 상태도 존재할거야" 
optional property는 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."

cheeseQuestion.response는 question이 초기화 되기전까지는 값을 가질 수 없는 상태이다. 위의 예제는 억지로 만들어낸 상황이라고 생각할 수 있지만, 코딩을 하다보면 위와 같은 상황은 언제든지 겪을 수 있다.

Assigning Constant Properties During Initialization
: initialization 과정중에 constant property에도 값을 할당하는 것이 가능하다. 만약 클래스의 경우라면 마찬가지로 initialization 과정중에 constant property에 값 할당이 가능하지만, subclass에서는 불가능하다.
위 SurveyQuestion의 text property를 let으로 변경하는것이 더 바람직해보인다. 한번 해보자.

class SurveyQuestion {
    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?"

beetsQuestion.response = "I also like beets. (But not with cheese.)"


Default Initializers
: Class나 Structure는 Default initializer를 가질 수 있다. 단, 조건이 있다. 모든 property들이 default 값을 가지고 있어야 한다. 그리고 다른 initializer를 제공하지 않아야 한다. 

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

var item = ShoppingListItem()

위 클래스의 name property는 optional이기 때문에 default nil값을 가진다. 그러므로 모든 property들이 default값을 가지게 되므로 default initializer를 통해 instance를 만들 수 있다.

Memberwise Initializers for Structure Types
: Structure는 다른 custom initializer를 제공하지 않으면 Memberwise Initializer로 instance를 생성할 수 있다. 모든 property를 초기화 하는 initializer이고 property 이름을 argument label로 사용한다. 

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

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

* 만약 당신이 만든 custom value type이 default initializer, memberwise initializer, custom initializer를 동시에 갖기를 원한다면, custom initializer를 extension에 선언하면 된다.



Initializer Delegation for Value Types
: initializer는 다른 initializer를 호출할 수 있다. 이것은 instance 초기화의 한 부분으로 동작하며, 여러개의 initializer가 존재할 때, 중복 코드 생성을 피하기 위함이다. 
delegation 동작은 value type(structure, enumeration)과 class type간에 차이가 존재한다. 그도 그럴것이 value type은 상속이 불가능하기 때문에 initializer delegation 동작이 비교적 심플하다. initializer delegation이 자기 자신 내에서만 이루어 지기 때문이다.
Class는 상속이 가능하기 때문에 Class는 슈퍼클래스의 property들까지 initialization 동안에 적절한 값으로 초기화 해줘야 하는 책임을 가지고 있다. 

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 () {}
    // default initializer랑 동작이 똑같지만 밑에 custom initializer와 같이써야 하기 때문에 custom initializer인것 처럼 보이기 위한 fake이다.

    init (origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    // memberwise initializer랑 동작이 똑같지만 밑에 custom initializer와 같이써야 하기 때문에 custom initializer인것 처럼 보이기 위한 fake이다.
    
    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)
    }
}

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

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)

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 
: 슈퍼클래스의 property들을 포함해서 클래스의 모든 property들은 initialization time에 적절한 값으로 초기화 되어야 한다. 
스위프트는 클래스 타입에 대한 두 종류의 initializer를 제공한다. designated initializer와 convenience initializer가 그것이다.

Designated Initializers and Convenience Initializers
: Designated Initializer는 현재 클래스의 모든 property들을 초기화 하고, 슈퍼 클래스의 property들을 초기화 하기 위해서 적절한 슈퍼클래스의 initializer를 호출해 준다.
대부분의 클래스는 하나의 Designated Initializer를 가지고, 왠만하면 2개를 넘지 않는다. 그리고 모든 클래스는 반드시 적어도 하나의 designated Initializer를 가지고 있어야 한다. 
: Convenience Initializer는 같은 클래스의 Designated Initializer를 호출해야 하는 책임을 가지고 있다. Convenience Initializer는 꼭 생성해야할 필요는 없다. 필요할때 생성해서 사용하면 된다. 

Syntax for Designated and Convenience Initializers
: Designated Initializer는 일반적인 initializer 선언방법과 동일하게 하면 된다.

init (parameters) {
    //statements
}

convenience initializer는 'convenience' 키워드가 앞에 붙는다.

convenience init (parameters) {
    //statements
}

Initializer Delegation for Class Types
: 스위프트는 initializer delegation에 대해 3가지 rule을 정의했다.

Rule 1
- designated initializer는 반드시 슈퍼클래스의 designated initializer를 호출해야 한다.
Rule 2
- convenience initializer를 반드시 같은 클래스의 다른 initializer를 호출해야 한다.
Rule 3
- convenience initializer는 결국 designated initializer를 호출하도록 구성되어야 한다.(즉,  convenience initializer끼리 메세지 체인이 일어나도 결국엔 마지막 convenience initializer는 designated initializer를 호출해야 한다.)


Two-Phase Initialization
: 스위프트는 Class initialization을 하는데 두 단계 과정을 거치도록 강제한다.
첫번째는, 모든 stored property들이 적절한 값으로 초기화 되어야 한다.
두번째는, 각 클래스들이 stored property들을 customize할 수 있는 기회를 갖는다. 
위 두단계는 순차적으로 이루어 져야하며, 두 번째 단계를 생략해도 된다. 

* 위 두 단계의 초기화 과정은 Objective-C의 초기화 과정과 비슷하지만, 가장 큰 차이는 첫번째 단계인 stored property를 초기화 하는 과정에 있다. Objective-C는 default로 모든 stored property들을 0 또는 nil로 초기화 시키지만, 스위프트는 개발자가 원하는 값으로 초기화 시킬 수 있다.

Safety check
: 스위프트의 compiler는 위 두 단계의 initialization 과정이 올바르게 이루어졌는지 체크하기 위해 4가지 Safety check를 한다.

Safety check1
- designated initializer가 슈퍼클래스의 designated initializer를 호출하기 전에 자신 클래스의 모든 stored property들이 적절한 값으로 초기화 되었는지 체크한다.
Safety check2
- designated initializer가 상속받은 property들의 값을 customize 하기전에 슈퍼클래스의 designated initializer를 호출해서 상속받은 property들을 초기화 하는지 체크한다.
만약 슈퍼클래스의 designated initializer가 불리기 전에 상속받은 property들을 customizing한다면, 슈퍼클래스의 designated initializer에서 해당값들이 overwritten될 수 있다.
Safety check3
- convenience initializer는 property(슈퍼 클래스의 property 포함)들에게 값을 지정하기 전에 다른 initializer를 미리 호출해야 한다. 
만약 그렇지 않으면,  초기화한 값이 다른 initializer에 의해 overwritten될 수 있다. 즉 convenience initializer는 property를 초기화 하는것이 아니라 property를 customizing하는 곳이다. 그러기 위해서는 미리 property들의 값을 초기화하는 작업이 선행되어야 한다.
Safety check4
- Phase1(모든 property들이 적절한 값으로 초기화 되는것)이 끝나기 전에 self를 참조할 수 없고, property를 참조하고 있는 instance method들을 호출할 수 없다.
그 이유는, 모든 property들이 적절한 값으로 초기화 되기 전까지 클래스의 instance는 준비된 상태가 아닌 불완전한 상태라고 판단하기 때문이다. 

Two-Phase Initialization In Detail
: 위 Safety check를 토대로 Two-Phase Initialization과정을 좀 더 상세히 들여다 보도록 하겠다.

Phase1
- designated 또는 convenience initializer가 호출된다.
- 클래스 인스턴스에 대한 메모리가 할당된다. 그렇지만 아직 초기화가 완료된 상태는 아니다.
- designated initializer를 통해 자신 클래스의 property에 대한 초기화를 진행한다.
- 슈퍼클래스의 designated initializer를 호출한다. 슈퍼클래스의 designated initializer 역시 자신의 property에 대한 초기화를 진행하고 다시 슈퍼클래스의 designated initializer를 호출해서 root 클래스까지 이 과정이 반복된다.
- root 클래스까지 initialization 과정이 완료되면 모든 property들의 초기화가 완료되었다는 것이고, 이는 클래스 인스턴스에 할당된 모든 메모리가 적절한 값으로 초기화 되었다는 것이다. 즉, Phase 1이 완료된다.

Phase2
- root 클래스에서 bottom 클래스까지 역으로 initialization 과정이 delegate down되면서 각 designated initializer는 필요하다면 property들에 원하는 값을 대입 시키거나 self 또는 다른 instance method 호출을 통해서 instance customizing 한다.
- 마지막으로 convenience initializer에서 instance를 customizing할 수 있는 기회는 갖는다.

Initializer Inheritance and Overriding
: 스위프트는 Objective-C와 다르게 슈퍼클래스의 initializer를 default로 상속받지 않는다. 그 이유는 스위프트는 클래스가 property의 default 초기값을 제공하지 않기 때문이다. 그래서 instance를 생성할 때 자동상속된 슈퍼클래스의 initializer를 호출한다면 자신 클래스의 property들을 초기화가 되지 않을 위험이 있다.
만약 슈퍼클래스의 designated initializer를 override 하기를 원한다면, 앞에 @override 키워드를 붙여줘야 한다. default initializer를 override할때도 마찬가지다.
@override 키워드를 붙이면 스위프트 컴파일러는 슈퍼클래스의 initializer와 매칭되는지 체크를 한다. 
슈퍼클래스의 convenience initializer와 매칭되는 initializer를 구현할때에는 앞에 @override 키워드를 붙이지 않는다.

Automatic Initializer Inheritance
: 자식클래스는 슈퍼클래스의 initializer를 자동으로 상속받지 않지만, 특별한 경우에 자동으로 상속받는다. 

Rule 1
- 자식클래스가 새로운 designated initializer를 제공하지 않았다면, 슈퍼클래스의 모든 designated initializer를 상속받는다. 

Rule 2
- 만약 자식클래스가 슈퍼클래스의 모든 designated initializer의 implementation을 제공 한다면(자동으로 상속받았거나, 실제로 override해서 custom implementation을 제공한 경우 둘다 해당된다. 또한 슈퍼클래스의 designated initializer를 자식클래스에서 convenience initializer로 구현한 경우도 해당된다. 단, 슈퍼클래스의 여러개의 designated initializer 중 자식클래스에서 일부만 override한 경우는 해당되지 않는다.) 슈퍼클래스의 모든 convenience initializer도 상속받는다.

위 두가지 rule은 자식클래스가 custom convenience initializer를 가지고 있어도 해당된다.

Designated and Convenience Initializers in Action
: 실제 Designated, Convenience Initializer들이 어떻게 동작하는지 알아보도록 하자.

class Food {
    var name: String  
    
    init (name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}


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



RecipeIngredient 클래스는 자신의 stored property를 초기화 시키는 designated initializer 가지고 있고, 부모클래스의 designated initializer를 override 해서 구현하고 있기 때문에 부모클래스의 convenience initializer를 자동으로 상속 받았다. 

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? "v" : "x"
        
        return output
    }
}



ShoppingListItem은 유일한 stored property인 purchased에 선언과 동시에 초기값을 제공하고 있기 때문에 이것을 초기화 해주는 designated initializer가 필요하지 않다. 
그리고 부모클래스의 어떤 designated initializer도 override하고 있지 않고, 자신의 custom designated initializer도 구현하지 않고 있기 때문에 부모클래스의 모든 designated initializer를 자동으로 상속 받는다. 또한 부모클래스의 모든 designated initializer를 구현하고 있기 때문에(자동 상속으로..) 부모 클래스의 모든 convenience initializer도 자동 상속받는다. 

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]

breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true

for item in breakfastList {
    print(item.description)
}

// 1 x Orange juice v
// 1 x Bacon x
// 6 x Eggs x  



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

[Swift 4.0] Deinitialization  (0) 2017.04.01
[Swift 4.0] Initialization(2)  (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 홍성곤
,

Introduction
: Class는 다른 Class의 method, property 그리고 다른 특성들을 상속받을 수 있다. 이때, 상속을 하는 Class는 Subclass라 부르고 상속 당하는 클래스를 Superclass라고 부른다.

Subclass는 Superclass의 method, property, subscript에 접근할 수 있으며 overriding 해서 동작을 customizing 할 수 있다. 
또한 Subclass에서 Superclass의 property observer를 만들 수 있다. 해당 property가 stored로 선언되었는지 computed로 선언되었는지 상관없이 observer를 만들 수 있다.


Defining a Base Class
: 어떤 Class도 상속받지 않은 Class를 Baseclass 라고 한다.

class Vehicle {

    var currentSpeed = 0.0

    var description: String {

        return "traveling at \(currentSpeed) miles per hour"

    }

    func makeNoise() {

        // do nothing - an arbitrary vehicle doesn't necessarily make a noise

    }

}


let someVehicle = Vehicle()


print("Vehicle: \(someVehicle.description)")

// Vehicle: traveling at 0.0 miles per hour


Subclassing
: Subclassing은 기존 Class를 상속해서 새로운 Characteristics를 더하는 것이다.

class SomeSubclass: SomeSuperclass {

    // subclass definition goes here

}

":" 뒤에 Superclass를 명시한다.

class Bicycle: Vehicle {

    var hasBasket = false

}


let bicycle = Bicycle()


bicycle.hasBasket = true

bicycle.currentSpeed = 15.0


print("Bicycle: \(bicycle.description)")

// Bicycle: traveling at 15.0 miles per hour


class Tandem: Bicycle {

    var currentNumberOfPassengers = 0

}


let tandem = Tandem()

tandem.hasBasket = true

tandem.currentNumberOfPassengers = 2

tandem.currentSpeed = 22.0

print("Tandem: \(tandem.description)")

// Tandem: traveling at 22.0 miles per hour


Overriding
: overriding은 inherit과 다르게 상속받은 instance method, type method, instance property, type property, subscript를 재정의(덮어쓰기) 하는 것이다. 
앞에 "override" 키워드를 붙여 표시한다.

Accessing Superclass Methods, Properties, and Subscripts
: Superclass의 method, property, subscript를 overriding된 각 구현부에서 접근할 수 있다.

- overriding된 Subclass의 method 구현부에서 "super" 키워드를 통해 Superclass의 override한 method를 접근할 수 있다.
- overriding된 Subclass의 property getter, setter 구현부에서 "super" 키워드를 통해 Superclass의 override한 property를 접근할 수 있다.
- overriding된 Subclass의 subscript 구현부에서 "super[someIndex]" 와 같은 문법으로 Superclass의 subscript에 접근할 수 있다.

Overriding Methods

class Train: Vehicle {

    override func makeNoise() {

        print("Choo Choo")

    }

}

let train = Train()

train.makeNoise()

// Prints "Choo Choo"

위와같이 "override" 키워드를 붙여서 해당 method를 override 했으며, 해당 instance는 override된 method를 호출한다.

Overriding Properties
: property를 overriding 하는 방법은 subclass에서 해당 property의 getter, setter를 재정의 하거나 property observer를 추가해서 custom한 동작을 구현하는 것이다.

Overriding Property Getters and Setters
property가 stored 인지 computed 인지 상관없이 Getter, Setter를 통해 overriding 할 수 있다.
Subclass 에서는 Superclass의 properties가 stored 인지, computed 인지 알지 못한다. 단지 property 이름과 type 정보만 알 뿐이다. 
override 할 때 해당 property의 이름과 type은 변경할 수 없다.
하지만  getter, setter를 제공해서 read-only property 에서 read-write property로 바꿀 수 있다.
그러나 read-write property를 read-only property로 바꿀 수 없다.

*property를 override 하려고 setter를 정의한 경우에는 getter도 함께 정의하여야 한다. getter의 동작은 override 하고 싶지 않은 경우에는 구현부에 super.someProperty를 단순히 return 하면 된다.

class Car: Vehicle {

    var gear = 1

    override var description: String {

        return super.description + " in gear \(gear)"

    }

}

let car = Car()

car.currentSpeed = 25.0

car.gear = 3

print("Car: \(car.description)")

// Car: traveling at 25.0 miles per hour in gear 3

Overriding Property Observers
property가 stored 인지 computed 인지 상관없이 Observer를 통해 overriding 할 수 있다.
다만, read-only property인 경우 overriding 할 수 없다. willset, didset method를 구현할 수 없기 때문이다.  또한 하나의 property에 setter와 property observer를 동시에 구현할 수 없다. property observer의 기능을 setter 안에서 모두 구현이 가능하기 때문이다.

class AutomaticCar: Car {

    override var currentSpeed: Double {

        didSet {

            gear = Int(currentSpeed / 10.0) + 1

        }

    }

}


let automatic = AutomaticCar()

automatic.currentSpeed = 35.0

print("AutomaticCar: \(automatic.description)")

// AutomaticCar: traveling at 35.0 miles per hour in gear 4


Preventing Overrides
: "final" 키워드를 method, property, subscript 앞에 붙여 overriding 되는것을 막을 수 있다. 
"final" 키워드는 extension 안에서의 method, property, subscript 에도 붙일 수 있다.
그리고 Class 앞에 "final" 키워드를 붙여서 해당 Class는 subclassing을 불가능하게 만들 수 있다. 


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

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

[Swift 4.0] Subscripts

IOS/Swift 2017. 4. 1. 23:17

Introduction
: subscript는 collection, list, sequence의 element에 접근하려고 할때 조금 더 편리한 방법을 제공하는 수단이다.
예를 들어 Array의 element를 접근할 때는 someArray[index], Dictionary의 element 접근할 때는 someDictionary[key]와 같이 subscript를 이용해서 편리하게 접근할 수 있다.

subscript는 Class, structure, enumeration에서 정의 가능하다.


Subscript Syntax
: "subscript" 키워드와 함께 정의한다. 그리고 하나이상의 input parameter와 return type을 정의해야 한다. 

subscript(index: Int) -> Int {

    get {

        // return an appropriate subscript value here

    }

    set(newValue) {

        // perform a suitable setting action here

    }

}

return value의 type은 newValue의 type과 동일하다. Computed property 처럼 newValue parameter를 생략할 수 있는데, 이 경우 "newValue"라는 이름이 자동할당 된다.

read-only 형식의 subscript도 정의할 수 있는데, 예시는 아래와 같다.

subscript(index: Int) -> Int {

    // return an appropriate subscript value here
}

set 메서드가 존재하지 않는다.
read-only subscript를 정의한 TimesTable 이라는 구조체를 만들어 보도록 하겠다.

struct TimesTable {

    let multiplier: Int

    subscript(index: Int) -> Int {

        return multiplier * index

    }

}

let threeTimesTable = TimesTable(multiplier: 3)

print("six times three is \(threeTimesTable[6])")

// Prints "six times three is 18"


Subscript Options
: Subscript는 input parameter 갯수의 제한은 없습니다. 이러한 parameter 들은 어떠한 type도 될 수 있고, 가변 파라미터 정의도 가능하다. 
그러나, in-out parameter, default parameter value는 사용이 불가능 하다.

Class, Structure는 multiple subscript를 구현할 수 있다. 이를 subscript overloading 이라 한다.

subscript는 보통 하나의 parameter를 받는게 일반적이지만, 필요에 따라 여러개의 parameter를 받도록 구현할 수 있다. 아래의 Matrix struct를 예로 들어보겠다.

struct Matrix {

    let rows: Int, columns: Int

    var grid: [Double]

    init(rows: Int, columns: Int) {

        self.rows = rows

        self.columns = columns

        grid = Array(repeating: 0.0, count: rows * columns)

    }

    func indexIsValid(row: Int, column: Int) -> Bool {

        return row >= 0 && row < rows && column >= 0 && column < columns

    }

    subscript(row: Int, column: Int) -> Double {

        get {

            assert(indexIsValid(row: row, column: column), "Index out of range")

            return grid[(row * columns) + column]

        }

        set {

            assert(indexIsValid(row: row, column: column), "Index out of range")

            grid[(row * columns) + column] = newValue

        }

    }

}


var matrix = Matrix(rows: 2, columns: 2)


matrix[0, 1] = 1.5

matrix[1, 0] = 3.2



let someValue = matrix[2, 2]

// this triggers an assert, because [2, 2] is outside of the matrix bounds

위 matrix structure는 2차열 배열과 아주 유사한 구조를 가지고 있고, 각각의 element에 접근하기 위해서 2개의 input parameter를 받는 subscript를 구현하고 있다.






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

[Swift 4.0] Initialization(1)  (0) 2017.04.01
[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
Posted by 홍성곤
,

[Swift 4.0] Methods

IOS/Swift 2017. 4. 1. 23:17

Introduction
: Class, Structure, Enumeration 모두 instance method, type method(class method)를 정의할 수 있다.


Instance Methods
: instance의 functionality를 담당한다. 

class Counter {

    var count = 0

    func increment() {

        count += 1

    }

    func increment(by amount: Int) {

        count += amount

    }

    func reset() {

        count = 0

    }

}


dot(.)을 통하여 instance method를 호출할 수 있다.

let counter = Counter()

// the initial counter value is 0

counter.increment()

// the counter's value is now 1

counter.increment(by: 5)

// the counter's value is now 6

counter.reset()

// the counter's value is now 0


The self Property
: 모든 instance는 "self"라는 property를 가지고 있다. "self"는 instance 자신이다. instance method 안에서 사용가능하며, 다른 instance method 호출이나 property에 접근할 때 사용한다. 

func increment() {

    self.count += 1

}


property에 접근할 때 property 앞에 무조건 "self"를 붙여줄 필요는 없지만, parameter 이름 또는 지역 변수 이름이 property 이름과 같은 경우 "self"를 property 앞에 붙여줘야 한다.

struct Point {

    var x = 0.0, y = 0.0

    func isToTheRightOf(x: Double) -> Bool {

        return self.x > x

    }

}

let somePoint = Point(x: 4.0, y: 5.0)

if somePoint.isToTheRightOf(x: 1.0) {

    print("This point is to the right of the line where x == 1.0")

}

// Prints "This point is to the right of the line where x == 1.0"


Modifying Value Types from Within Instance Methods
: Structure와 Enumeration은 Value type 이다. 그래서 기본적으로는 자신의 instance method 안에서 property 값을 변경할 수 없다. 변경이 가능하도록 하기 위해서는 property를 변경하는 instance method 앞에 "mutating" 이라는 키워드를 명시해야 한다. 


struct Point {

    var x = 0.0, y = 0.0

    mutating func moveBy(x deltaX: Double, y deltaY: Double) {

        x += deltaX

        y += deltaY

    }

}

var somePoint = Point(x: 1.0, y: 1.0)

somePoint.moveBy(x: 2.0, y: 3.0)

print("The point is now at (\(somePoint.x), \(somePoint.y))")

// Prints "The point is now at (3.0, 4.0)"


위 mutating method moveBy는 해당 instance를 let으로 선언하면 호출할 수 없다.

let fixedPoint = Point(x: 3.0, y: 3.0)

fixedPoint.moveBy(x: 2.0, y: 3.0)

// this will report an error


Assigning to self Within a Mutating Method
: mutating method는 instance 자신도 바꿀 수 있다.


struct Point {

    var x = 0.0, y = 0.0

    mutating func moveBy(x deltaX: Double, y deltaY: Double) {

        self = Point(x: x + deltaX, y: y + deltaY)

    }

}


structure 뿐만 아니라 Enumeration의 mutating method도 마찬가지다.

enum TriStateSwitch {

    case off, low, high

    mutating func next() {

        switch self {

        case .off:

            self = .low

        case .low:

            self = .high

        case .high:

            self = .off

        }

    }

}

var ovenLight = TriStateSwitch.low

ovenLight.next()

// ovenLight is now equal to .high

ovenLight.next()

// ovenLight is now equal to .off


Type Methods 
: Type Method는 다른 언어의 Class Method 라고 보면 된다.
선언 방법은 메서드 앞에 "static" 키워드를 붙이거나 "class" 키워드를 붙이면 된다. static method는 자식 클래스에서 override 하지 못하고, class method는 자식 클래스에서 override가 가능하다.

이 둘을 통틀어서 Type Method 라고 하는데, Type Method 안에서 참조하는 self는 instance가 아니라 Type 그 자체를 의미한다. 

class SomeClass {

    class func someTypeMethod() {

        // type method implementation goes here

    }

}

SomeClass.someTypeMethod()


* instance Method나 type Method안에서 다른 method를 호출할 때 또는 다른 property를 접근할 때 "self" 키워드를 앞에 안붙여도 된다. 
* Type Method와 Instance Method 이름을 같게해서 선언할 수 있다.
* Instance Method 안에서 Type Method를 호출할 때 또는 Type property를 접근할 때  타입이름을 앞에 붙어야 한다. 


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

[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
Posted by 홍성곤
,

[Swift 4.0] Properties

IOS/Swift 2017. 4. 1. 23:16

Introduction
: property는 class, structure, enumeration의 속성을 나타내는 값이다. Stored property는 변수와 상수를 저장하는 용도로 쓰이고, computed property는 저장하는 용도 보다는 값을 계산하는 용도로 쓰인다. 그리고 Stored property는 class, structure에만 쓰일수 있지만, Computed property는 class, structure, enumeration에서 모두 쓰일 수 있다.


Stored Properties
: instance를 구성하는 값을 저장하는 용도로 사용하는 property이다. 선언과 동시에 값을 할당할 수 있으며 let으로 선언된 상수라 하더라도 initialization 과정에서 값을 변경할 수 있다.
class, structure만 사용이 가능하다.

struct FixedLengthRange {

    var firstValue: Int

    let length: Int

}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)

// the range represents integer values 0, 1, and 2

rangeOfThreeItems.firstValue = 6

// the range now represents integer values 6, 7, and 8

Stored Properties of Constant Structure Instances
: structure를 let으로 선언하면 var property라 하더라도 property 값을 변경하지 못한다.

let
rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// compile 에러 발생!!

이는 structure가 value type 이기 때문이다. value type이 constant로 선언되면 property들 또한 constant 성질을 가진다.

Lazy Stored Properties
: lazy stored property는 처음 사용되기 전까지 해당 property가 초기값을 가지지 않는 성질을 가지고 있다.
lazy stored property는 반드시 var로 선언되어야 한다. let으로 선언 한다는 의미는 initialization이 끝나기 전에 초기값을 가지고 있어야 한다는 뜻이다. 그러나 lazy stored property는 initialization이 끝난 이후에 값을 가지기 때문이다.

lazy property는 외부 요인에 의해서 initialization 과정이 끝난 이후에 해당 값을 알 수 있을경우 사용하면 유용하다. 또한, lazy property가 initialization 과정에서 하기에는 부담스러운 작업을 가지고 있을경우, 즉, 필요할 때만 해당 작업을 하도록 하기 위해서 사용하는 데에도 유용하다.

class
DataImporter {
    /*
     DataImporter is a class to import data from an external file.
     The class is assumed to take a nontrivial amount of time to
initialize.
     */

    var filename = "data.txt"

    // the DataImporter class would provide data importing functionality here

}


class DataManager {

    lazy var importer = DataImporter()

    var data = [String]()

    // the DataManager class would provide data management functionality here

}


let manager = DataManager()

manager.data.append("Some data")

manager.data.append("Some more data")

// 아직 DataImporter instance는 만들어지지 않았다.

위에 예제에서 data.txt 파일에 있는 데이터를 DataManager의 data 배열에 넣지 않을 거라면 굳이 DataImporter를 생성할 필요가 없다. DataImporter의 initialization 작업이 굉장히 무거울 것이기 때문에 lazy property로 생성하여 instance 생성을 꼭 필요할때만 하도록 한것이다.

print
(manager.importer.filename)
// Prints "data.txt"
// 이때 DataImporter instance가 생성된다.

* lazy property가 멀티 스레드에서 접근이 가능하고, 해당 property가 아직 초기화 되지 않았다면, property가 한번만 초기화 되리라고는 보장하지 못한다.


Computed Properties
: 실제로 값을 저장하지 않고 getter, optional setter를 이용해서 stored property를 계산해서 원하는 값을 get, set하는 용도로 사용한다.

struct Point {

    var x = 0.0, y = 0.0

}

struct Size {

    var width = 0.0, height = 0.0

}

struct Rect {

    var origin = Point()

    var size = Size()

    var center: Point {

        get {

            let centerX = origin.x + (size.width / 2)

            let centerY = origin.y + (size.height / 2)

            return Point(x: centerX, y: centerY)

        }

        set(newCenter) {

            origin.x = newCenter.x - (size.width / 2)

            origin.y = newCenter.y - (size.height / 2)

        }

    }

}

var square = Rect(origin: Point(x: 0.0, y: 0.0),

                  size: Size(width: 10.0, height: 10.0))

let initialSquareCenter = square.center

square.center = Point(x: 15.0, y: 15.0)

print("square.origin is now at (\(square.origin.x), \(square.origin.y))")

// Prints "square.origin is now at (10.0, 10.0)"

Rect의 center는 computed property이다. 자신의 stored property인 origin과 size를 이용해서 원하는 값을 추출하거나 세팅하고 있다. 

Shorthand Setter Declaration
: computed property의 setter로 전달되는 파라미터의 이름을 명시하지 않으면, Swift가 해당 파라미터 이름을 "newValue"로 지정한다. 

struct AlternativeRect {

    var origin = Point()

    var size = Size()

    var center: Point {

        get {

            let centerX = origin.x + (size.width / 2)

            let centerY = origin.y + (size.height / 2)

            return Point(x: centerX, y: centerY)

        }

        set {

            origin.x = newValue.x - (size.width / 2)

            origin.y = newValue.y - (size.height / 2)

        }

    }

}

Read-Only Computed Properties
: computed property에 setter가 없으면 당연히 read-only property가 된다. 
그리고 computed property는 애초에 값이 하나로 고정된 것이 아니기 때문에 var로 선언해야 한다.(read-only property 포함)

struct Cuboid {

    var width = 0.0, height = 0.0, depth = 0.0

    var volume: Double {

        return width * height * depth

    }

}

let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)

print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")

// Prints "the volume of fourByFiveByTwo is 40.0"


Property Observers
: property observer는 property의 값이 변경되면 자동으로 invoke 되어 호출된다. 심지어 기존값과 값은값이 set 되더라도 invoke 된다.
property observer는 자신이 정의한 모든 stored property에 적용이 가능하다.(lazy stored property는 적용 불가능 하다.) 또한 상속된 stored, computed property를 overriding 해서 observer를 적용할 수도 있다. 다만, 자신이 만든 computed property 에는 observer를 만들 필요가 없다. setter에서 observing을 하면 되기 때문이다. 
willSet과 didSet 메서드를 통해 observing을 할 수 있다.

willSet은 값이 저장되기 직전에 invoke되고 didSet은 값이 저장된 직후에 invoke 된다. 둘다 구현해도 되고 하나만 구현해도 된다.
willSet의 default parameter 이름은 "newValue"이고 didSet의 default parameter 이름은 "oldValue"이다. 직접 parameter 이름을 지정할 수도 있다.

super class property의 willSet, didSet observer 메서드는 child class의 initialization 과정에서 값을 변경하면 invoke 되지만, 자신의 class initialization 과정에서 property가 초기화 될때는 invoke 되지 않는다.

class StepCounter {

    var totalSteps: Int = 0 {

        willSet(newTotalSteps) {

            print("About to set totalSteps to \(newTotalSteps)")

        }

        didSet {

            if totalSteps > oldValue  {

                print("Added \(totalSteps - oldValue) steps")

            }

        }

    }

}

let stepCounter = StepCounter()

stepCounter.totalSteps = 200

// About to set totalSteps to 200

// Added 200 steps

stepCounter.totalSteps = 360

// About to set totalSteps to 360

// Added 160 steps

stepCounter.totalSteps = 896

// About to set totalSteps to 896
// Added 536 steps


*observer를 가지고 있는 property가 in-out parameter로 넘겨지면, observer method는 무조건 불린다. in-out parameter가 동작하는 copy-in copy-out 모델에 의해 메서드가 종료될 때 property에 값이 다시 쓰여지기 때문이다. 


Global and Local Variables
: Global, local 변수도 computing, ,observing 변수로 정의하는 것이 가능하다. 

* Global 변수 및 상수는 lazily stored property처럼 생성되는 시점이 delay 된다. 다만, 앞에 "lazy" 키워드를 붙이지 않아도 된다.


Type Properties
: Type Property는 클래스 변수라고 생각하면 된다. instance의 갯수와는 무관하게 클래스내에 하나의 set만 존재하는 변수이다. let으로 선언한 Type Property는 C의 static constant와 비슷하고 var로 선언한 Type Property는 C의 static variable과 비슷하다.

stored instance property와 다르게 stored type property는 default 값을 가지고 있어야 한다. 
type property는 instance init 시점에 생성되는것이 아니기 때문에 default 값을 넣어줄만한 타이밍이 딱히 존재하지 않기 때문이다.
그리고 stored type property는 lazy initialized 되더라도 멀티 스레드에서 동시 접근해도 한번만 init 되는것을 보장한다.

Type Property Syntax
: C와 Objective-C 에서는 static 변수, 상수를 global static 변수, 상수로 선언하여 global scope를 가졌는데, Swift 에서는 Type Property로 선언하여 해당 scope를 클래스 내부로 한정시킨다.

선언 문법은 Type property 앞에 "static" 키워드를 붙이면 된다. 다만, computed type property의 경우 자식클래스에서 override가 가능하도록 "class" 키워드를 대신 붙여도 된다.

struct
SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}

enum SomeEnumeration {

    static var storedTypeProperty = "Some value."

    static var computedTypeProperty: Int {

        return 6

    }

}

class SomeClass {

    static var storedTypeProperty = "Some value."

    static var computedTypeProperty: Int {

        return 27

    }

    class var overrideableComputedTypeProperty: Int {

        return 107

    }

}

Querying and Setting Type Properties
: type property에 접근할때는 앞에 Type이름 뒤에 dot을 붙여서 접근한다.

print(SomeStructure.storedTypeProperty)

// Prints "Some value."

SomeStructure.storedTypeProperty = "Another value."

print(SomeStructure.storedTypeProperty)

// Prints "Another value."

print(SomeEnumeration.computedTypeProperty)

// Prints "6"

print(SomeClass.computedTypeProperty)

// Prints "27"




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

[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
[Swift 4.0] Closures  (0) 2017.04.01
Posted by 홍성곤
,

Introduction
: Objective-C 와는 달리 Swift는 헤더파일과 구현파일을 나누지 않는다. 구현파일에다가 코드를 작성하면 Swift는 각 메서드, 멤버변수의 접근자에 따라 자동으로 헤더파일을 만들어 준다.

그리고 Swift의 Class와 Structure는 다른 언어에 비해 object로서의 역할 보다 functionality의 역할에 더 치우쳐져 있다.


Comparing Classed and Structures
1) 공통
 - property를 정의할 수 있다.
 - method를 정의할 수 있다.
 - subscript를 정의할 수 있다.
 - initializer를 구현할 수 있다.
 - extension을 구현할 수 있다.
 - protocol을 confirm할 수 있다.

2) 차이점
 - Class만 상속이 가능하다.
 - Type Casting이 가능하다.
 - Deinitializer를 구현할 수 있다.
 - Reference Counting이 가능하다. (Structure는 어떤 방식으로든 다른곳에 전달될 때, reference count를 증가시키는 것이 아니라 복사된다.)

Definition Syntax 

struct Resolution {
    var width = 0
    var height = 0
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

Class and Structure Instances
: class와 structure의 instance를 생성하는 문법이다.

let someResolution = Resolution()
let someVideoMode = VideoMode()

Accessing Properties
: property에 access할 때는 dot syntax를 사용한다.

print("The width of someResolution is \(someResolution.width)")
//Prints "The width of someResolution is 0"

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"


Structures and Enumerations Are Value Types
: value type이 의미하는 것은 variable이나 constant에 할당되거나 다른 function으로 전달될 때 값이 복사되어 진다는 것이다.

모든 Structure, Enumeration은 value typed이다.

integers, floating-point numbers, Booleans, strings, arrays, dictionaries 모두 structure, 즉, value type이다.

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

cinema.width = 2048

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

위의 예처럼 값이 복사되는 것을 알수 있다. 이는 Enumeration 에도 똑같이 적용된다.

enum CompassPoint {
    case north, south, east, west
}

var currentDirection = CompassPoint.west
let rememberDirection = currentDirection

currentDirection = .east

if rememberedDirection == .west {
    print("The remembered direction is still .west")
}
// Prints "The remembered direction is still .west"


Classes Are Reference Types
: reference type은 value type과 달리 copy 되는것이 아니라 같은 instance를 참조한다.

let tenEighty = VideoMode()

tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty

alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"

Identity Operators
- Identical to(===), Not Identical to(!==)
- 두 contants 또는 variables가 같은 Class의 instance를 참조하고 있는지 판별한다.

* "=="는 두 instance가 의미상으로 같은지 체크한다. 이것은 클래스 디자인에 따라 equal 되는 조건이 틀려진다.


Choose Between Classes and Structures
: Class와 Structure 중에 어떤것을 써야할지 고민될 때는 애플의 가이드 라인을 참고하면 된다.

Structure를 사용하기 적합한 경우
 - Structure의 주요 목적이 간단한 데이터들을 encapsulation 하는 것일때.
 - instance가 다른곳으로 전달될 때 encapsulated value들이 reference 되는것 보다 copy 되는것이 합리적일때
 - 모든 property가 value type이고 reference 되는것 보다 copy 되기를 원할 때
 - 상속을 사용하지 않을 경우.


Assignment and Copy Behavior for Strings, Arrays. and Dictionaries
: value type은 다른곳으로 전달될 때 무조건 복사되는 것이 아니라, 필요할 때만 복사된다. 예를들어 value type이 다른곳으로 전달된 이후에 상태가 하나도 변경되지 않았다면 굳이 복사해서 메모리에 2개를 가지고 있을 필요가 없다. 이러한 경우 복사를 하지 않고 reference 하고 있다가 상태가 변경될때 복사해서 상태를 변경한다.






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

[Swift 4.0] Methods  (0) 2017.04.01
[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
[Swift 4.0] Closures  (0) 2017.04.01
[Swift 4.0] Functions  (0) 2017.04.01
Posted by 홍성곤
,

Introduction
: Enumeration은 관련있는 value들의 group으로 하나의 타입을 정의하는 것을 말한다. Enumeration을 통해 type-safe way로 코드작성이 가능하다.

Swift의 Enumeration은 C의 Enumeration 보다는 조금 더 flexible하다. 모든 case에 value를 할당할 필요가 없다. value의 타입은 string, character, 모든 integer 또는 floating-point 종류의 타입이 가능하다.


Enumeration Syntax

enum SomeEnumeration {
    // enumeration definition goes here
}

예제 코드이다.

enum CompassPoint {
    case north
    case south
    case east
    case west
}

다른 언어의 Enumeration과 달리 각각의 case는 default integer 값을 가지지 않는다.

enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

multiple value들은 single line으로 표현이 가능하다.

var directionToHead = CompassPoint.west

directionToHead = .east

directionToHead는 CompassPoint 타입으로 추론되었기 때문에 추후 .east 같이 앞에 타입을 생략해서 값 할당이 가능하다.


Matching Enumeration Values with a Switch Statement
: Enum을 Switch 문에서 쓰는 법을 알아보도록 하겠다.

directionToHead = .south

switch directionToHead {
    case .north:
        print("Lots of planets have a north")
    case .south:
        print("Watch out for penguins")
    case .east:
        print("Where the sunrises")
    case .west:
        print("Where the skies are blue")
}
// Prints "Watch out for penguins"


Associated Values
: Swift의 enumeration은 각각의 case에 관련된 value들을 가질 수 있게 해준다. 예제를 보자.

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)

productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
    case .upc(let numberSystem, let manufacturer, let product, let check):
        print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
    case .qrCode(let productCode):
        print("QR code: \(productCode).")
}

// Prints "QR code: ABCDEFGHIJKLMNOP."

associated value 들이 모두 var거나 let이면 앞에 한번만 명시하면 된다.

switch productBarcode {
    case let .upc(numberSystem, manufacturer, product, check):
        print("UPC:\(numberSystem), \(manufacturer), \(product), \(check).")
    case let .qrCode(productCode):
        print("QR code: \(productCode).")
}

// Prints "QR code: ABCDEFGHIJKLMNOP."


Raw Values
: enumeration의 case는 각각 다른 타입의 associate value 들을 가질 수 있었다. 뿐만 아니라, 같은 타입의 Raw value들 또한 정의할 수 있다. 예제를 보자.

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

Raw Value는 string, characters, integer or floating-point 종류의 타입으로 정의할 수 있다. 그리고 Raw Value는 enumeration 안에서 unique한 값이어야 한다. 

implicitly Assigned Raw Values
: raw value 타입이 Integer 또는 String 이라면, Swift는 자동으로 raw value를 대입해준다. integer일 경우 바로 앞의 case의 값에 1을 더한값이 자신의 raw value가 되고, first case의 raw value가 명시되어 있지 않으면 0이 된다. 

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

위의 경우 Planet.mercury는 1이고 Planet,venus는 2이다.

enum compassPoint: String {
    case north, south, east, west
}

위의 경우 raw value 타입이 String이고 아무값도 지정하지 않았기 때문에 raw value는 case 이름을 따라간다. CompassPoint.south의 raw value는 "south" 이다. 

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"

Initializing from a Raw Value
: enumeration이 raw value 타입과 함께 정의되었다면 initializer의 param으로 rawValue값을 받아서 해당 enumeration case 또는 nil을 return 한다. 

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus

위 예제에서 rawValue 파라미터에 대한 case가 없을때를 nil을 return 한다. 

let positionToFind = 11

if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
        case .earth:
            print("Mostly harmless")
        default:
            print("Not a safe place for humans")
        }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"


Recursive Enumerations
: case의 associated value로 하나 또는 그 이상의 enumeration case를 포함하는 경우를 뜻한다. 이 경우 "indirect"를 명시해야 한다. 

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)    
}

모든 case에 대한 indirect을 허용하려면 enum 앞에 "indirect"을 명시하면 된다.

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)    
}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_expression: ArithmeticExpression) -> Int {
    switch expression {
        case let .number(value)
            return value
        case let.addition(left, right):
            return evaluate(left) + evaluate(right)
        case let .multiplication(left, right):
            return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
// Prints 18


*Enumeration은 inherits를 할 수 없다.(structure도 마찬가지로 불가능.) 하지만 protocol을 confirm하는 형식으로 inherits 비슷하게 구현이 가능하다.
아래 예제를 보자.


protocol TableSection {

    static var rows: [Self] { get }

    

    var title: String { get }

    

    var mandatoryField: Bool { get }

}


extension TableSection {

    var mandatoryTitle: String {

        if mandatoryField {

            return "\(title)*"

        } else {

            return title

        }

    }

}


enum RegisterTableSection: Int, TableSection {

    case Username

    case Birthdate

    case Password

    case RepeatPassword

    

    static var rows: [RegisterTableSection] {

        return [.Username, .Password, .RepeatPassword]

    }

    

    var title: String {

        switch self {

        case .Username:

            return "Username"

        case .Birthdate:

            return "Date of birth"

        case .Password:

            return "Password"

        case .RepeatPassword:

            return "Repeat password"

        }

    }

    

    var mandatoryField: Bool {

        switch self {

        case .Username:

            return true

        case .Birthdate:

            return false

        case .Password:

            return true

        case .RepeatPassword:

            return true

        }

    }

}


class ViewController: UITableViewController {

    

    override func viewDidLoad() {

        super.viewDidLoad()

    }

    

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

        return 1

    }

    

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return RegisterTableSection.rows.count

    }

    

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        guard let row = RegisterTableSection(rawValue: indexPath.row) else {

            // This should never happen

            return UITableViewCell()

        }

        

        let cell = UITableViewCell()

        cell.textLabel?.text = row.mandatoryTitle

        return cell

        

    }

}


위 코드를 실행하면 tableView에 3개의 cell이 나타나게 되고, 각 cell에는 순서대로, "Username*", "Date of birth", "password*"이 나타나게 된다.


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

[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Closures  (0) 2017.04.01
[Swift 4.0] Functions  (0) 2017.04.01
[Swift 4.0] Control Flow  (0) 2017.04.01
Posted by 홍성곤
,

[Swift 4.0] Closures

IOS/Swift 2017. 4. 1. 23:15

Introduction
: Swift의 Closure는 C와 Obj-C의 Block, 다른 language의 lambda와 비슷하다.


Closure Expressions
: Closure는 의미의 명확성을 해치지 않는 범위 내에서 축약되어 표현될 수 있다.
지금부터 예제 메서드인 sorted(by:)가 변화하는 과정을 살펴 보면서 어떤식으로 축약될 수 있는지 살펴 보겠다.

The Sorted Method
: Swift의 standard library는 sorted(by:)라는 메서드를 제공한다. 이 메서드는 메서드로 넘어오는Closure를 기반으로 array의 값들을 정렬한다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

var reverseNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

알파벳 순서대로 내림차순 정렬된다.

Closure Expression Syntax
: Closure는 다음과 같은 형식을 지닌다.

{ ("parameters") -> "return type" in 
    statements
}

parameters는 in-out parameter가 될수 있고, variadic parameter로 지정해서 사용할 수도 있다. 또한 Tuple을 parameter와 return type으로 지정할 수 있다.

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
 })

Inferring Type From Context
: 위의 parameter로 넘겨진 closure의 경우 type을 명시하지 않더라도 추론할 수 있다. 왜냐하면 names array가 String type의 value를 다루기 때문에 closure의 type은 (String, String) -> Bool이 되어야 한다.
그래서 다음과 같이 생략해서 표현할 수 있다.

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })

이처럼 Closure는 다른 function이나 method로 넘겨질 때 항상 type 추론될 수 있기 때문에 위의 예처럼 축약형 표현이 가능하다.

Implicit Returns from Single-Expression Closures
: 위 closure는 'return' 키워드 또한 생략할 수 있다.

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

위 예제의 경우 Closure가 반드시 Bool 값을 return 해야 한다. 그리고 Closure의 body는 single expression( s1> s2)만 포함하고 있기 때문에 'return' 키워드를 생략할 수 있는것이다.

Shorthand Argument Names
: Swift의 Closure는 각 argument에 대해서 shorthand 버젼을 제공한다. argument 순서대로 $0, $1, $2 ... 형식으로 제공된다.
shorthand argument를 사용하면 argument의 정의를 생략할 수 있다.

reversedNames = names.sorted(by: { $0 > $1 } )

Operator Methods
: Swift의 String type은 '>' operator의 구현을 operator method로 제공하고 있다. 해당 operator method는 두개의 String type을 argument로 받고, Bool을 return 한다. 이는 sorted(by:)의 argument type과 동일하므로 다음과 같이 축약해서 사용가능하다.

reversedNames = name.sorted(by: >)

operator method는 'Advanced Operators' 챕터에서 배우도록 하겠다.


Trailing Closures
: 만약 어떤 function의 마지막 argument가 closure이고 그 closure의 내용이 상당히 길다면 Trailing Closure형태로 표현할 수 있다. 
예제를 보자.

func someFunctionThatTakesAClosure(closure: () -> void) 
{
    
}

someFunctionThatTakesAClosure(closure: {
    
})
// 일반적인 function 호출 방법이다.

someFunctionThatTakesAClosure() {

}
// trailing closure 표현법을 사용한 function 호출 방법이다.

위에서 예로 든 sorted(by:)를 trailing closure 표현법으로 호출해 보겠다.

reversedNames = names.sorted() { $0 > $1 }

() 도 생략할 수 있다.

reversedNames = names.sorted { $0 > $1 }


Capturing Values
: closure는 closure안에 있는 상수와 변수를 capture 한다. 그래야만 capture한 상수, 변수의 scope이 끝나서 없어지더라도 미리 capture 했기 때문에 closure 안에서는 계속 사용할 수 있는 것이다. 예제를 보자.

func makeIncrementer(forIncrement amount: Int) -> () -> Int 
{
    var runningTotal = 0

    func incrementer() -> Int
    {
        runningTotal += amount
    
        return runningTotal
    }

    return incrementer
}

위에 함수에서 incrementer closure(또는 Nested Function)는 makeIncrementer 에서 선언 및 argument로 넘어온 runningTotal, amount를 capture 한다. 그래서 makeIncrementer call이 끝나고 runningTotal, amount의 scope를 벗어나더라도 incrementer closure 안에서는 해당 변수 및 상수들을 계속 사용할 수 있는것이다.

let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

let incrementBySeven = makeIncrementer(forIncrement: 7)

incrementBySeven()
// returns a value of 7

incrementByTen()
// returns a value of 40

* 만약 closure를 class instance의 property로 할당하고, closure가 자신이 속한 class instance 또는 instance의 멤버 변수를 capture함으로써 자신이 속한 class instance를 캡쳐하고 있다면, strong reference cycle이 만들어 지게 된다.
이런 문제를 회피하는 방법은 'Automatic Reference Counting' 챕터에서 자세히 배울 것이다.


Closure Are Reference Types
: 위 예에서 incrementBySeven, incrementByTen은 let 으로 선언되어 constant 이지만, 이 constant들이 참조하고 있는 closure는 reference type 이기 때문에 캡쳐 하고 있는 변수인 runningTotal의 값을 증가시킬 수 있다. 이것은 function과 closure는 referenceType 이기 때문이다.


Escaping Closures
: 우리가 정의하는 많은 함수들이 비동기 operation을 수행한다. 그리고 block을 매개변수로 받아 비동기 operation이 완료되면 해당 block을 호출함으로써 비동기 operation이 완료되었다는 것을 호출자에게 알려준다. 이때 함수는 operation이 끝나기전에 반환 되지만, block은 operation이 끝날때까지 반환되면 안되기 때문에 Swift에서는 @escaping 라는 syntax를 정의했다. 즉, 함수의 범위를 벗어나서 함수가 반환 되더라도 나중에 호출될 수 있는 block 이라는 것을 가리키는 것이다.

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping() -> Void) {
    completionHandlers.append(completionHandler)
}

위 코드에서 @escaping 을 빼게되면 compile error가 발생한다. 

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()

instance.doSomething()
print(instance.x)
//Prints "200"

completionHandlers.first?()
print(instance.x)
//Prints "100"

위 코드에서 @escaping 선언되어 있는 block 에서는 self를 명시적으로 capturing 해야 한다.(개인적인 생각은 @escaping 되면서 self를 자동으로 인식할 수 있는 범위를 벗어나서 명시해줘야 하는것 같다.) self를 명시하지 않으면 compile error가 발생한다.


AutoClosures
: AutoClosure는 함수에 인자로 전달되는 표현식을 wrapping 하기 위해 자동 생성되는 Closure이다. 아무런 인자도 가지지 않고, 호출될때 내부에서 wrapping된 표현식의 값을 반환한다. 이 구문은 명시적인 Closure 대신에 일반 표현식으로 작성해서 함수의 매개변수 주변의 중괄호를 생략할 때 편리하다. 
이것을 이용해서 closure가 만들어질때 바로 실행하는 것이 아니라 closure가 호출될 때 실행되기 때문에 delaying evaluation이 가능하다.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

let customerProvider = { customersInLine.remove(at : 0) }
print(customersInLine.count)
// Prints "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print("customersInLine.count")
// Prints "4"

위 코드와 같이 autoClosure가 생성될 때 로직이 실행되는 것이 아니라, autoClosure가 호출될 때 실행된다. 

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(customer: { customersInLine.remove(at: 0) })
// Prints "Now serving Alex!"

함수의 매개변수로 전달될 때도 실제 autoClosure가 호출될 때 로직이 실행된다.

@autoclosure syntax를 사용해서 parameter로 String Argument 인것 처럼 전달 하지만 자동으로 AutoClosure를 만들어서 위 코드와 동일한 동작을 하도록 만들 수 있다.
아래 코드를 보자.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoClosure () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

* @autuclosure syntax를 자주 남용하면 읽기 어려운 코드를 유발할 수 있다. 사용하더라도 네이밍을 적절히 하여 해당 argument가 autoclosure 인지 명확히 표시해야 한다. 

만약, autoClosure가 escape되야 한다면, @autoclosure와 @escaping을 같이 명시해야 한다. 

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"


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

[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
[Swift 4.0] Functions  (0) 2017.04.01
[Swift 4.0] Control Flow  (0) 2017.04.01
[Swift 4.0] Collection Types  (0) 2017.02.03
Posted by 홍성곤
,