Initializer in Swift - 3

권승용(Eric)·2024년 11월 1일

TIL

목록 보기
5/38

Initializer Inheritance and Overriding

  • Objective-C와는 다르게, Swift 서브클래스들은 부모 클래스 초기자를 기본적으로 상속하고 있지 않다.
    • 안전하고 적절한 특정한 상황에서만 부모 클래스 초기자들이 상속된다.
  • 슈퍼클래스와 같은 초기자를 작성하고 싶으면 서브클래스에 커스텀 구현을 작성하면 된다.
  • 만약 슈퍼클래스의 지정 초기자와 일치하는 서브클래스 초기자를 작성한다면 해당 지정 초기자에 대한 오버라이드를 제공하는 것이기 때문에 override 모디파이어를 작성해야 한다.
    • 이는 자동으로 제공되는 기본 초기자를 오버라이딩 할 때도 동일하게 적용됨.

서브클래스의 초기자 구현이 편의 초기자인 경우에도 슈퍼클래스의 지정 초기자를 재정의하는 경우엔 항상 override 모디파이어를 작성해야 한다.

  • 반대로 슈퍼클래스의 편의 초기자와 일치하는 서브클래스 초기자를 작성할 땐 override 모디파이어를 작성하지 않아도 된다.
    • 슈퍼클래스의 편의 초기자는 서브클래스의 초기자에서 호출될 수 없기 때문에 엄밀히 말하자면 슈퍼클래스 초기자에 대한 override를 제공하는 것은 아니기 때문이다.
  • override init 할 땐 super.init 호출해줘야함
  • 그렇지 않은 경우엔 super.init이 암시적으로 호출될 때도 있음
class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() implicitly called here
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}
  • 서브클래스는 상속된 변수는 초기화 과정에서 변경 가능하지만, 상속된 상수는 변경할 수 없다.

초기자 자동 상속

  • 기본적으로 서브클래스는 슈퍼클래스 초기자를 상속받지 않는다.

  • 그러나 특정한 조건 아래에서 자동적으로 상속된다.

  • 이는 많은 일반적인 상황에서는 override 초기자를 작성할 필요가 없다는 뜻이고, 안전할 경우 슈퍼클래스 초기자를 적은 노력으로 상속가능하다는 뜻이다.

  • 서브클래스의 모든 새로운 프로퍼티에 기본값을 제공한다고 할 때 아래 두 규칙이 적용된다:

    • 1번 규칙 - 서브클래스가 아무런 지정 초기자도 정의하지 않는다면, 자동적으로 슈퍼클래스의 모든 지정 초기자를 상속받는다.
    • 2번 규칙 - 만약 서브클래스가 모든 슈퍼클래스 지정 초기자에 대한 구현을 제공한다면 - 1번 규칙에 의해 상속받거나, 정의의 일부분으로 커스텀 구현을 제공하거나 해서 - 서브클래스는 슈퍼클래스의 모든 편의 초기자를 자동으로 상속받는다.
  • 이러한 규칙들은 서브클래스가 추가적인 편의 초기자를 추가하는 경우에도 적용된다.

서브클래스는 2번 규칙을 준수하는 것의 일부분으로 슈퍼클래스의 지정 초기자를 편의 생성자로 구현할 수 있다.

실패 가능한 초기자

  • 가끔은 초기화 과정이 실패할 수도 있도록 클래스, 구조체, 열거형을 정의하는 것이 유용할 때도 있다.

실패 가능한 초기자와 일반 초기자를 동일한 매개변수 타입과 이름으로 정의할 수 없다.

  • 실패 가능한 초기자는 초기화 하는 타입의 옵셔널 타입을 생성한다.

엄밀히 말하면 초기자는 값을 반환하지 않는다. 그보다는 초기화가 끝날 때까지 self가 완전하고 올바르게 초기화되었는지 확인하는 역할을 한다. 초기화 실패를 트리거하기 위해 nil 반환값을 작성하지만 초기화 성공을 나타내기 위해 return 키워드를 사용하지는 않는다.

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

let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not 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"

열거형의 실패 가능한 초기자

  • 인자에 기반해 적절한 열거형 케이스를 선택하고 싶을 때 실패 가능한 초기자를 사용할 수 있다.
  • 매칭 실패할 경우 초기화 실패 가능
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 isn't a defined temperature unit, so initialization failed.")
}
// Prints "This isn't a defined temperature unit, so initialization failed."

원시값과 함께 써보기

  • 원시값을 가지는 열거형은 자동으로 실패 가능한 초기자인 init?(rawValue:)를 가진다.
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 isn't a defined temperature unit, so initialization failed.")
}
// Prints "This isn't a defined temperature unit, so initialization failed."

초기화 실패 전파하기

  • 클래스, 구조체, 열거형의 실패 가능한 초기자는 같은 클래스, 구조체, 열거형의 다른 실패 가능한 초기자를 통해 위임(delegate)될 수 있다.
  • 서브클래스의 실패 가능한 초기자는 슈퍼클래스의 실패 가능한 초기자로 위임(delegate)될 수 있다.
  • 두 경우 모두, 실패하는 초기자를 위임하면 전체 초기화 과정이 즉시 실패하고, 코드가 더 이상 실행되지 않는다.

실패 가능한 초기자는 일반 초기자도 위임 가능하다. 실패하지 않는 기존 초기화 프로세스에 잠재적인 실패 상태를 추가해야 하는 경우 이 방법을 사용하면 됨.

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

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

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"

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

실패 가능한 초기자 덮어쓰기

  • 슈퍼클래스의 실패 가능한 초기자를 서브클래스에서 오버라이딩 할 수 있다.
  • 슈퍼클래스의 실패 가능한 초기자를 서브클래스의 일반 초기자로 오버라이딩 할 수도 있다.
  • 실패 가능한 초기자를 일반 초기자로 오버라이딩 할 경우 슈퍼클래스로 delegate up 하는 유일한 방법은 실패 가능한 슈퍼클래스 초기자를 강제 언래핑하는 것이다.

실패 가능한 초기자를 일반 초기자로 오버라이딩 할 수는 있지만 그 반대는 불가능하다.

init! 실패 가능한 초기자

  • 적절한 타입의 암시적으로 래핑되지 않은 선택적 인스턴스를 생성하는 실패할 수 있는 이니셜라이저를 정의할 수도 있다.
  • 이렇게 하려면 init 키워드 뒤에 물음표 대신 느낌표(init!)를 넣으면 된다.
  • init?에서 init!으로 또는 그 반대로 위임 가능.
  • init?을 init!으로 재정의하고 그 반대도 가능하다.
  • init에서 init!으로 델리게이트도 가능.(오류는 계속 남)
profile
ios 개발자에용

0개의 댓글