본문 바로가기

iOS 개발/Swift 문법

[iOS/Swift] Initializer 정리 (Swift 공식 문서)

https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

 

Initialization — The Swift Programming Language (Swift 5.5)

Initialization Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization t

docs.swift.org

 

 

목차 (눌러서 이동)

 

    Value Types 를 위한 Initializer Delegation

    Swift 의 Value type 인 Structures and Enumerations 을 위한 대리초기자에 대한 설명이다. (Class type 과 동작 방식이 다르기 때문에 구분) 

     

    Value types 은 상속을 지원하지 않아 상대적으로 간단하다. 해당 Value type 스스로가 제공하는 또 다른 초기자에게만 위임할 수 있다. 사용 방법은 Custom Initializer 에서 self.init 구문으로 다른 초기자를 호출한다.

     

    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( ) 은 Default Initializer 로 이를 호출할 시 Properties 가 기본값으로 초기화된다. 두 번째 init(origin: Point, size: Size) 는 기능적으로 Memberwise Initializer 와 같다. 

     

    Memberwise Initializer 는 구조체를 위한 기본 초기자로 예를 들어, 위의 Size 구조체는 초기자가 없지만 아래처럼 초기화할 수 있다.

     

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

     

    세 번째 init(center: Point, size: Size) 이 대리 초기자를 부르는 초기자인데, 인자로 받은 값으로 원점을 계산한 뒤 두 번째 초기자에게 값을 넘기며 초기화를 대리하도록 한다. 세 번째 초기자 자체에서 Properties 를 초기화할 수도 있지만, 이미 있는 기능을 활용하지 않을 이유는 없다.

    Class types 를 위한 Initializer Delegation

    Class types 에서 상속받는 모든 Stored Properties 는 초기자의 실행이 끝마치기 전에 초기화되어야 한다. Swift 는 이를 보장하기 위해 Designated Initializer 와 Convenience Initializer 를 정의해 놓았다.

    Designated Initializer

    Designated Initializer 는 Class 의 주요 초기자이다. 즉 여기에서 모든 Properties 를 초기화하고 적절히 상위 Class 의 초기자를 호출하여 상속 관계의 모든 초기화 작업을 진행하게 한다. 

     

    모든 Class 는 적어도 하나의 Designated Initializer 를 가져야 하고 이 조건은 초기자를 상속받는 방식으로도 만족시킬 수 있다.

     

    init(parameters) {
    	statements
    }

    Convenience Initializer

    Convenience Initializer 는 초기자를 보조하는 역할을 한다. Convenience Initializer 를 만들어 Designat ed Initializer 를 호출할 수 있다. 보조 초기자이므로 필요할 때만 작성한다.

     

    convenience init(parameters) {
    	statements
    }

    Initializer Delegation Rules

    Designated 와 Convenience 초기자의 관계를 단순화 하기 위해 Swift 는 다음과 같은 규칙을 정해두었다.

     

     1. Designated 초기자는 상위 Class 의 Designated 초기자를 호출해야 한다.

     2. Convenience 초기자는 같은 Class 의 다른 Convenience 초기자를 호출해야 한다.

     3. Convenience 초기자는 궁극적으로 Designated 초기자를 호출해야 한다.

     

     

    Subclass 의 Designated 초기자는 모두 Superclass 의 Designated 초기자를 호출하고 (Rule 1), Convenience 초기자는 자신의 Class 내의 다른 초기자를 호출하며 (Rule 2), 두 Class 모두 Convenience 초기자가 궁극적으로 Designated 초기자를 호출하고 있다. (Rule 3)

     

    간단히 말해서, Designated 초기자항상 Delegate Up (상위 Class 에게 초기화 과정을 위임한다.) 하고, Convenience 초기자항상 Delegate Across(현재 Class 의 다른 초기자에게 과정을 위임한다.) 한다.

    Two-Phase Initialization

    Swift 의 Class 초기화 과정은 두 단계로 나뉜다. 첫 단계에서 모든 Stored Property 는 해당 Class 에서 제공하는 초기값이 할당된다. 두 번째 단계에서 Stored Properties 는 또 다른 값으로 할당될 기회가 주어진다. 

     

    두 단계의 초기화는 안전과 유연성을 동시에 제공한다. 값이 초기화 되기 전에 접근이 일어나는 것을 막고, 다른 초기자에 의해 예기치 않게 다른 값이 할당되는 것을 막는다. Swift Compiler 는 두 단계의 초기화를 안전하게 완료하기 위해 4 가지 사항을 확인한다.

     

    1. Designated Initializer 는 상위 Class 의 초기자를 호출하기 전 자신의 모든 Properties 를 초기화해야 한다. (객체 Memory 는 Properties 의 초기 상태가 모두 결정되어야 준비된 상태로 취급된다.)

     

    2. Designated Initializer 는 반드시 상위 Class 의 초기자를 호출한 후에 상속받은 Properties 를 초기화해야 한다. 그렇지 않으면 하위 초기자가 할당한 값이 상위 초기자에 의해 덮어씌어질 수 있다.

     

    3. Convenience Initializer 는 반드시 다른 초기자를 호출한 뒤 Property 값을 초기화할 수 있다. 그렇지 않으면 Convenience Initializer 가 초기화한 값이 다른 초기자에 의해 덮어씌어질 수 있다. 

     

    4. Initializer 는 초기화의 첫 단계 (모든 Stored Properties 가 초기값이 결정됨) 가 끝나기 전에 Instance methods 를 호출하거나, Instance properties 의 값을 읽거나, self 의 값을 참조할 수 없다.

     

    위 4 가지 사항을 염두에 두고 두 단계의 초기화 과정을 자세히 살펴보자.

    1 단계 

     1. Designated 혹은 Convenience Initializer 가 호출되고 객체를 위한 Memory 가 할당된다. (할당은 되지만 아직 사용할 수 없다.)

     

     2. Designated Initializer 는 해당 Class 의 모든 Stored Properties 를 모두 초기화 시킨다. Convenience 가 먼저 호출된다면 Properties 를 초기화하지 않고 다른 초기자를 먼저 호출한다. (이 Stored Properties 는 Memory 는 이제초기화가 완료되고 사용될 수 있다.) 

     

     3. Designated Initializer 는 상위 Class 의 초기자를 호출해 그들 자신의 Stored Properties 를 초기화 하도록 한다. (최상위 상속 Class 까지 도달.)

     

     4. 최상위 Class 까지 모든 Properties 의 초기화를 완료하면 객체 Memory 는 완전히 초기화된 것이고 1 단계가 끝난다.

     

    2 단계

     1. 상위 Class 부터 내려오며 Designated Initializer 들은 객체를 추가로수정할 기회를 가지고 여기서는 self 키워드(객체 Memory 가 1 단계에서 초기화 완료되었으므로) 를 통해 Properties 를 수정하거나 Instance methods 를 호출할 수 있다.

     

     2. 마지막으로 가장 먼저 호출되었던 Convenience Initializer 가 객체를 수정할 기회를 가진다. (역시 self 키워드를 사용할 수 있다.)

     

    Initializer Inheritance and Overriding

    기본적으로 Swift 의 하위 Class 는 상위 Class 의 초기자를 상속받지 않는다. (간소하게 구현된 상위 Class 의 초기자가 복잡한 하위 Class 를 불완전하게 초기화함을 막기 위한 것.)

     

    상위 Class 의 Designated Initializer 를 상속받고 싶다면 override 키워드와 함께 똑같은 형태의 초기자를 작성하면 된다. (Designated 를 하위 Class 에서 Convenience 로 override 할 수도 있다.)

     

    Convenience Initializer 는 하위 Class 에서 직접적으로 호출되지 않으므로 이를 상속할 때는 override 키워드를 사용하지 않는다

     

    하위 Class 에 Designated Initializer 가 없다면 자동으로 상위 Class 의 Designated Initializer 모두를 상속받는다.

     

    하위 Class 가 상위 Class 의 Designated Initializer모두 상속받고 있다면 자동으로 모든 Convenience Initializer 를 상속받는다.

     

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

     

    RecipeIngredient 는 Food 의 모든 Designated Initializer 를 상속하고 있으므로 모든 Convenience Initializer 역시 상속받는다. 상속받은 초기자는 상위 Class 가 아닌 하위 Class 의 초기자를 호출한다. (모든 Designated 가 상속되어야 Convenience 를 상속하므로 하위 Class 에서도 역시 호출할 Designated Initializer 가 override 되어 구현되어 있다.)

     

    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

    어떤 상황에서는 Class, Structure, Enumeration 의 초기화가 실패할 수도 있다. init? 키워드로 실패할 수도 있는 초기자를 선언할 수 있다. (일반적인 초기자와 같은 매개변수를 동시에 사용할 수는 없다.) 초기화가 실패해야 하는 지점에서 return nil 을 함으로써 실패했음을 알린다.

     

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

     

    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
            }
        }
    }
    
    // Enums with raw values automatically receive a failable initializer.
    enum TemperatureUnit: Character {
        case kelvin = "K", celsius = "C", fahrenheit = "F"
    }

    Required Initializers

    Initializer 선언에 required 키워드를 붙이면 모든 하위 Class 에서 해당 초기자를 반드시 구현해야 한다. 하위 Class 에서 required 초기자 구현 시 override 대신 required 키워드를 사용한다.

     

    class SomeClass {
    	required init() { ... }
    }
    
    class SomeSubclass: SomeClass {
    	required init() { ... }
    }

     

    만약 상속받은 다른 Initializer 로 해당 Requirement 를 만족할 수 있다면 명시적으로 required init() 을 작성할 필요는 없다.

    'iOS 개발 > Swift 문법' 카테고리의 다른 글

    [iOS/Swift] KeyPath  (0) 2021.04.14
    [iOS/Swift] Optional type, guard let vs. if let  (0) 2021.04.12