Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

sanichdaniel의 iOS 개발 블로그

initializer 본문

swift

initializer

sanich8355 2020. 9. 26. 17:49
뷰컨을 생성할때 인자를 넣어주고 싶을때가 있다.
그래서 생성자를 만들어주고 인자를 주입하면 2가지 에러가 뜬다.
1. super.init isn't called on all paths before returning from initilizer
2. 'required' initializer 'init(coder:)' must be provided by subclass of 'UIViewController'

swift 공식 doc를 읽으며 위 2가지 에러가 왜 뜨는지 알아보자!

Initialization 개념

인스턴스를 사용하기전에 준비하기. 저장 프로퍼티의 기본값을 주고, 필요에 앞서 이것저것 세팅을 해준다

 

Memberwise Initializer

strut는 명시적으로 initializer 명시하지 않는 이상 디폴트로 갖는다. 

 

Initializer Delegation

Initializers 는 다른 인스턴스의 생성자를 호출할 수 있다. 
Value 타입의 경우 delegation은 간단한 편인데, class type 인 경우 상속을 지원하기에 delegation이 복잡해진다.

 

Class 상속과 Initialization

Swift의 class 타입은 2가지 종류의 생성자를 지원합니다.

지정 생성자와 편의 생성자

Designated(지정) Initializer

* 편의 생성자가 아닌 생성자들. 

* 자신의 클래스의 모든 프로퍼티의 값을 세팅해주고, 적절한 슈퍼 클래스의 생성자를 호출해줍니다.

* 모든 클래스는 하나 이상의 지정 생성자가 있어야하고, 때론 자동 생성자 상속으로 이 조건이 충족되기도 한다

In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass, as described in Automatic Initializer Inheritance below.

 

Convenience(편의) Initializer

편의 생성자는 보조적인 역할. 편의 생성자가 지정 생성자를 호출한다

convenience가 아니면 다 designated인가?

 

Class Type Initializer Delegation 규칙


1) 지정생성자는 부모의 지정생성자를 호출해야한다

2) 편의 생성자는 같은 클래스에서 다른 생성자를 호출해야한다

3) 편의 생성자는 궁극적으로 지정생성자를 호출해야한다

 

지정 생성자는 위로 대리하고

편의 생성자는 옆으로 대리한다

 

Two-Phase Initialization

class initialization은 2가지 단계로 이뤄집니다.

첫번째 단계에서는 모든 저장 프로퍼티가, 자신이 소개된 class에서 값이 세팅이 됩니다. 

모든 저장 프로퍼티에 값이 세팅이 되면 2번째 단계가 진행이 됩니다. 이 단계에서 class는 값을 커스터마이징 할 수 있습니다.

Swift 컴파일러는 아래 4가지 단계를 걸쳐 initialization이 잘 일어나는지 체크합니다.

 

Safety check 1
지정 생성자는 부모 클래스의 생성자를 호출하기전에, 클래스의 모든 프로퍼티들을 세팅해야한다

 

Safety check 2

지정 생성자는 상속받은 프로퍼티를 세팅하기 전에, 부모 클래스 생성자를 호출해야한다.

아니면 덮어씌워진다

 

Safety check 3

편의생성자는 프로퍼티를 세팅하기전에 다른 생성자를 호출해야한다. 

 

Safety check 4

생성자는 first phase initialization이 끝나기전에,  인스턴스 메서드를 호출하거나, 인스턴스 프로퍼티 값을 읽거나, self를 값으로 접근하는게 불가능하다. 

 

Phase 1

* 지정 생성자 또는 편의 생성자가 호출된다

* 인스턴스를 위한 메모리가 할당되었지만 initilize되지는 않았다

* 지정 생성자를 통해 

* 지정 생성자가 부모 클래스 생성자를 호출한다

* 체인이 위로 끝까지 가서 모든 저장 프로퍼티에 값이 있을때 인스턴스의 메모리가 initialized 되었다고 판단되고 phase 1이 끝난다

모든 부모 클래스의 프로퍼티가 값을 할당받으면, 메모리가 initialize 되었다고 판단되고, phase 1 이 끝난다

Phase 2

* 체인을 내려오면서 각 지정 생성자는 인스턴스를 커스터마이즈 한다. 생성자는 이제 self, 인스턴스 메소드, 인스턴스 프로퍼티를 접근 가능하다

* 편의 생성자는 이제 인스턴스를 커스터마이즈 할 수 있다.

Initializer Inheritance and Overriding

Swift에서 자식 클래스는 부모 클래스의 생성자를 자동으로 상속받지는 않는다.

지정생성자를 상속받아 사용하고 싶은 경우에는 override를 붙이고 사용해야한다. 

자동으로 생성되는 디폴트 생성자를 오버라이딩 하는 경우도 마찬가지

 

편의 생성자를 상속 받는 경우에는, 부모 클래스의 편의 생성자는 자식 클래스에서 절대 직접적으로 호출될수없다. 

엄밀히 말하면 오버라이드를 하는게 아니기에 override를 안붙인다.

 

만약 자식 클래스의 생성자가 phase2에서 따로 커스터마이즈를 안하고, 슈퍼 클래스가 0개의 인자를 받는 지정 생성자라면, super.init() 호출을 생략할 수 있다

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() 암묵적 생략
        // 만약 super.init() 생략되었는데, 커스터마이제이션 (ex. self 접근 등등) 하면 에러 난다.
    }
}

 

자동 생성자 상속

특정 조건이 맞는다면, 자식 클래스는 슈퍼클래스의 생성자를 자동으로 상속받을 수 있다. 

 

Rule 1

자식 클래스가 지정 생성자가 없다면, 자동으로 모든 슈퍼 클래스의 지정 생성자를 상속받는다.

반대로 자식 클래스가 지정 생성자가 있다면, 어떠한 생성자도 상속받지 않는다.

Rule 2

자식 클래스가 슈퍼클래스의 모든 지정 생성자를 구현한다면 - Rule 1에 의한 상속 또는 override - 슈퍼 클래스의 모든 편의 생성자를 상속받는다.  

 

Designated and Convenience Initilizers in Action

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

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

RecipeIngredient 는 부모 클래스의 모든 지정 생성자를 구현하였다. - init(name: String) 
따라서 부모의 모든 편의 생성자를 상속받는다. 

아래 사진에서 보면 RecipeIngredient가 init() 을 가지고 있는것을 확인할수 있다.

 

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

ShoppingListItem은 RecipeIngredient를 상속받고, 모든 프로퍼티에 디폴트 값을 주고, 스스로 생성자를 정의하지 않았기에 부모의 모든 지정, 편의 생성자를 상속받는다. 

Failable Initializers

Initialization이 실패하는 경우를 정의하는게 유용할때가 있다. 

init? 으로 선언한다

Note
failable과 nonfailable 생성자는 같은 인자 타입과 이름을 가질 수 없다

Failiable Intializer은 옵셔널 타입의 값을 반환한다.
생성을 실패하는 경우에는 return nil 해주면 된다

엄밀히 말하면 생성자는 값을 반환하지 않는다. 생성자의 역할은 self가 온전히 생성되는걸 보장할뿐이다. 

 

Failable Initilizers for Enum

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

Failable Initializers for Enum with raw values

raw Value가 있는 enum은 자동으로 failable initilizer을 갖는다. init? (rawValue:)

 

Initialization 실패 전파하기

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

 

Overriding a Failable Initializer

슈퍼 클래스의 failable 생성자를 오버라이드 할 수 있다. 또한 슈퍼 클래스의 failable 생성자를 nonfailable 생성자로 오버라이드 할 수 있다. 

만약 failable 슈퍼클래스 생성자를 nonfailable하게 오버라이드 했다면, 강제 언래핑 !을 해서 부모 클래스 전파를 할수 있다. 

 

Required Initilizer

모든 자식 클래스가 특정 생성자를 구현해야할 경우 required modifier를 붙여준다

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}

class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

 

만약 자식 클래스가 있을때, 해당 생성자를 상속 받거나 구현하기를 강제한다. 

자식 클래스 자신만의 지정 생성자를 구현한다면 부모 생성자들을 상속 못받게 된다.

 

반대로 생성자들을 상속받는다면 굳이 required 생성자를 추가할 필요가 없다.

 

UIViewController 상속받고 init() 생성자 추가했을때 required 생성자도 추가해줘야 했던 이유!

생성자를 따로 추가하면서, 생성자들을 자동으로 상속받지 못하게 되고 required 구현해야했다. 

 

 

의문들 정리하기 

1. super.init isn't called on all paths before returning from initilizer
2. 'required' initializer 'init(coder:)' must be provided by subclass of 'UIViewController'


1. 이 발생했던 이유는 기존에는 명시한 지정생성자가 없었으니 자동으로 생성자를 상속받았으나

새로 지정생성자를 추가하면서 생성자들을 상속받지 못하게 되었고, 부모 지정생성자를 호출할 의무가 생긴것이다.
2. 은 위에서...

 

출처 

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

'swift' 카테고리의 다른 글

enum vs protocol as UseCase  (0) 2020.09.28
Type Erasure  (0) 2020.09.28
Self Type  (0) 2020.09.05
type(of:)  (0) 2020.09.05
Metatype  (0) 2020.09.03