[Swift 공식문서 읽기]Initialization

llim🧚🏻‍♀️·2021년 8월 19일
1

Swift

목록 보기
14/26
post-thumbnail

안녕하세요. 엘림입니다🙇🏻‍♀️

Swift 공식 문서를 정독하기 시리즈입니다!

제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!

좀 더 편하게 보기위해 한국어로 번역된 사이트를 함께 확인했습니다!ㅎㅎ

자, 그럼 시작해볼까요

이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏


초기화

초기화는 클래스, 구조체, 열거형 인스터스를 사용하기 위해 준비 작업을 하는 단계입니다. 이 단계에서 각 저장 프로퍼티의 초기 값을 설정합니다.
초기화 과정은 initializer를 정의하는 것으로 구현할 수 있으며, 초기화와 반대로 여러 값과 자원의 해지를 위해 deinitializer도 사용할 수 있습니다.

저장 프로퍼티를 위한 초기값 설정

인스턴스의 저장 프로퍼티는 사용하기 전에 반드시 특정 값으로 초기화 돼야 합니다.
(initializer에서 저장 프로퍼티에 값을 직접 설정하면 프로퍼티 옵저버가 호출되지 않고 값 할당이 수행됩니다.)

이니셜라이저

이니셜라이저는 특정 타입의 인스턴스를 생성합니다. 이니셜라이저의 가장 간단한 형태는 파라미터가 없고 init 키워드를 사용하는 것입니다.

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"

기본 프로퍼티

프로퍼티의 선언과 동시에 값을 할당하면 그 값을 초기 값으로 사용할 수 있습니다.
항상 같은 초기 값을 갖는 다면 기본 프로퍼티를 사용하는 것이 좋습니다. 이 기본값은 상속시 함께 상속 됩니다.

struct Fahrenheit {
    var temperature = 32.0
}

커스터마이징 초기화

초기화 파라미터

이니셜라이저 정의에 파라미터를 정의해 사용할 수 있습니다.

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

파라미터 이름과 인자 레이블

이니셜라이저는 메소드 이름을 지정하지 않고 이니셜라이저 식별자로 파라미터를 사용합니다.
모든 파라미터는 인자 레이블을 갖는데 만약 사용자가 이 레이블을 지정하지 않으면 Swift가 자동으로 하나를 할당해 제공합니다.

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)

// let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required

인자 레이블이 없는 이니셜라이저 파라미터

평범한 메소드와 마찬가지로, 인자 레이블을 생략하는 것이 더 명료한 경우 _ 기호를 사용해 이니셜라이저에서 인자 레이블을 생략할 수 있습니다.

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

옵셔널 프로퍼티 타입

프로퍼티의 최초 값이 없고 나중에 추가될 수 있는 값을 옵셔널로 선언해 사용할 수 있습니다. 옵셔널 프로퍼티는 자동으로 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."

초기화 중에 상수 프로퍼티 할당

이니셜라이저에서는 상수 프로퍼티에 값을 할당하는 것도 가능합니다.
클래스 인스턴스에서 상수 프로퍼티는 초기화 중 그 클래스 안에서만 변경이 가능하고 서브클래스에서는 변경이 불가능 합니다.
프로퍼티를 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.)"

기본 이니셜라이저

모든 프로퍼티에 초기값이 설정되어 있고, 이니셜라이저를 하나도 정의하지 않았다면,
Swift는 모든 프로퍼티를 기본 값으로 초기화하는 기본 이니셜라이저를 제공해 줍니다.

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

구조체의 멤버사이드 이니셜라이저

멤버쪽 이니셜 라이저는 프로퍼티가 기본값이 없어도 따로 이니셜라이저를 정의하지 않았다면 제공됩니다. 이는 선언한 모든 프로퍼티를 인자로 사용합니다. (== 정의한 이니셜라이저가 있다면 제공되지 않습니다.)

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

값 타입의 이니셜라이저 위임

이니셜라이저에서 다른 이니셜라이저를 호출할 수 있습니다. 이 과정을 이니셜라이저 위임이라고 하는데요.
값 타입(structures, enumerations)과 클래스 타입(class)간 이니셜라이저 위임 동작이 다릅니다. 값 타입은 상속을 지원하지 않아 이니셜라이저를 자기 자신의 다른 이니셜라이저에만 사용할 수 있습니다. 반면 클래스 타입은 상속이 가능하기 때문에 superclass의 이니셜라이저를 subclass에서 호출 가능합니다.

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

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)

마지막 초기화init(center: Point, size: Size)를 보면, 내부에서 다른 초기자인 init(center: Point, size: Size)를 불러와 사용하는 것을 볼 수 있습니다.

만약 마지막 이니셜라이저만 익스텐션을 이용해 선언하고, 첫번째 두번째 이니셜라이저를 생성하지 않아도, 첫번째, 두번째 이니셜라이저는 자동으로 생성되고 익스텐선에 선언한 이니셜라이저도 사용할 수 있습니다.

클래스 상속과 초기화

모든 클래스의 저장 프로퍼티와 superclass로부터 상속받은 모든 프로퍼티는 초기화 단계에서 반드시 초기값이 할당돼야 합니다. Swift에서는 클래스 타입에서 모든 프로퍼티가 초기 값을 갖는 것을 보장하기 위해 2가지 방법을 지원합니다.

지정(Designated) 이니셜라이저와 편리한(Convenience) 이니셜라이저

지정 이니셜라이저는 클래스의 기본 이니셜라이저입니다. 지정 초기자는 클래스의 모든 프로퍼티를 초기화합니다. 또한 적절한 슈퍼클래스 이니셜라이저를 호출하여, 슈퍼클래스까지 체이닝 된 초기화 프로세스를 진행합니다. 클래스 타입은 반드시 한 개 이상의 지정 이니셜라이저가 있어야 합니다.

편리한 이니셜라이저는 초기화 단계에서 미리 지정된 값을 사용해서, 최소한의 입력으로 초기화를 할 수 있도록 해주는 보조 이니셜라이저입니다.

클래스 타입을 위한 이니셜라이저 위임

지정 초기자와 편리한 초기자 사이의 관계를 단순하게 하기 위해, Swift는 이니셜라이저는 다음 3가지 규칙을 따라 이니셜라이저를 위임합니다.

규칙 1 지정 초기자는 반드시 직계 superclass의 지정 초기자를 호출해야 합니다.
규칙 2 편리한 초기자는 반드시 같은 클래스의 다른 초기자를 호출해야 합니다.
규칙 3 편리한 초기자는 궁극적으로 지정초기자를 호출해야 합니다.

정리하자면, 지정 초기자는 반드시 위임을 superclass로 해야하고, 편리한 초기자는 반드시 위임을 같은 레벨에서 해야합니다.

위를 보면 Subclass의 편리한 초기자는 같은 레벨에서 다른 지정 초기자를 호출하고 지정 초기자는 초기화를 상위 클래스에게 위임해 상위 클래스의 지정 초기자가 호출되는 것을 확인할 수 있습니다.


따로 적혀있진 않지만, 위 클래스가 아래 클래스의 슈퍼 클래스입니다.

2단계 초기화

Swift에서 클래스 초기화는 2단계로 진행됩니다. 첫번째 단계에서는 각 저장된 프로퍼티는 초기값으로 초기화 됩니다. 모든 저장된 프로퍼티의 상태가 결정되면 두번째 단계가 시작됩니다. 두번째 단계에서는 새로운 인스턴스의 사용이 준비됐다고 알려주기 전에 저장된 프로퍼티를 커스터마이징하는 단계입니다.

(Swift의 2단계 초기화는 Objective-C에서의 초기화와 유사합니다. 주된 차이점은 첫번째 단계입니다. Objective-C에서는 모든 프로퍼티에 zero 혹은 null 값을 할당합니다. Swift의 초기화는 좀더 유연해서 커스텀한 초기 값을 할 당할 수 있습니다. 그리고 0과 nil이 잘못된 초기값으로 지정된 경우 대처할 수 있습니다.)

Swift의 컴파일러는 2단계 초기화가 에러없이 끝나는 것을 보장하기 위해 4단계 안전 확인(safety-check)을 합니다.

안전 확인 1단계
지정 초기자는 클래스 안에서 초기화를 superclass의 초기자에게 위임하기 전에 모든 프로퍼티를 초기화 해야 합니다. 위에서 언급한 것 처럼 메모리에서 객체는 모든 저장된 프로퍼티가 초기 상태를 갖어야만 완전히 초기화 된것으로 간주되기 때문에 이 규칙을 만족시키기 위해 지정 초기자는 반드시 다른 초기자로 넘기기 전에 소유하고있는 모든 프로퍼티를 초기화 해야 합니다.

안전 확인 2단계 : 지정 초기자는 반드시 상속된 값을 할당하기 전에 superclass의 초기자로 위임을 넘겨야 합니다. 그렇지 않으면 상속된 값이 superclass이 초기자에 의해 덮어 쓰여지게 됩니다.

안전 확인 3단계 : 편리한 초기자는 반드시 어떤 프로퍼티를 할당하기 전에 다른 초기자로 위임을 넘겨야 합니다. 만약 그렇지 않으면 편리한 초기자에 의해 할당된 값을 다른 클래스의 지정 초기자에 의해 덮어 쓰여지게 됩니다.

안전 확인 4단계 : 이니셜라이저는 초기화의 1단계가 끝나기 전에는 self의 값을 참조하거나 어떤 인스턴스 프로퍼티, 메소드 등을 호출하거나 읽을 수 없습니다.

위 네 가지 안전 검사를 기반으로, 2단계 초기화가 실행되는 방식은 아래와 같습니다.


1단계

  • 클래스에서 지정 또는 편의 이니셜라이저가 호출됩니다.
  • 해당 클래스의 새 인스턴스에 대한 메모리가 할당됩니다. 메모리가 아직 초기화되지 않았습니다.
  • 해당 클래스의 지정된 이니셜라이저는 해당 클래스에 의해 도입된 모든 저장된 속성에 값이 있음을 확인합니다. 이제 이러한 저장된 속성에 대한 메모리가 초기화됩니다.
  • 지정된 이니셜라이저는 자신의 저장된 속성에 대해 동일한 작업을 수행하기 위해 수퍼클래스 이니셜라이저에 전달합니다.
  • 이것은 체인의 맨 위에 도달할 때까지 클래스 상속 체인을 계속합니다.
  • 체인의 맨 위에 도달하고 체인의 마지막 클래스가 저장된 모든 속성에 값이 있음을 확인하면 인스턴스의 메모리가 완전히 초기화된 것으로 간주되고 1단계가 완료됩니다.


2 단계

  • 체인의 맨 위에서 다시 아래로 작업하면 체인의 지정된 각 초기화 프로그램에는 인스턴스를 추가로 사용자 정의할 수 있는 옵션이 있습니다. 이니셜라이저는 이제 self속성 에 액세스 하고 속성을 수정하고 인스턴스 메서드를 호출하는 등의 작업을 수행할 수 있습니다.
  • 마지막으로, 체인의 모든 편의 이니셜라이저는 인스턴스를 사용자 정의하고 self로 작업할 수 있는 옵션이 있습니다.

이니셜라이저의 상속과 오버라이딩

Swift에서는 기본적으로 subclass에서 superclass의 이니셜라이저를 상속하지 않습니다. 이유는 superclass의 이니셜라이저가 무분별하게 상속돼 복잡하게 돼 subclass에서 이것들이 잘못 초기화 되는 것을 막기 위함입니다.

만약 클래스에서 모든 프로퍼티의 초기 값이 지정돼 있고 아무런 커스텀 초기자를 선언하지 않았다면 기본 초기자 init()을 사용할 수 있습니다.
superclass의 초기자를 오버라이드 하기 위해서는 subclass에서 그 초기자에 override 키워드를 붙이고 재정의 합니다.

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)

자동 이니셜라이저 상속

서브클래는 슈퍼클래스의 이니셜라이저를 기본적으로 상속하지 않지만 특정 상황에서 자동으로 상속 받습니다.
사실 많은 상황에서 이니셜라이저를 오버라이드 할 필요가 없습니다. 서브클래스에서 새로 추가한 모든 프로퍼티에 기본 값을 제공하면, 다음 두 가지 규칙이 적용됩니다.

규칙1 서브클래스가 지정초기자를 정의하지 않으면 자동으로 수퍼클래스의 모든 지정초기자를 상속합니다.
규칙2 서브클래스가 수퍼클래스의 지정초기자를 모두 구현한 경우(규칙 1의 경우 혹은 모든 지정초기자를 구현한 경우) 자동으로 수퍼클래스의 편리한 초기자를 추가합니다.

지정초기자와 편리한 이니셜라이저의 사용

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

let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"

let mysteryMeat = Food()
// mysteryMeat's name is "[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)

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

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 ✔
// 1 x Bacon ✘
// 6 x Eggs ✘

💡 제가 헷갈렸던 부분을 제 스타일대로 정리를 해보았습니다.
1. 슈퍼클래스에 있는 이니셜라이저와 같은 매개변수를 받는 이니셜라이저를 쓰면 앞에 오버라이드를 붙여서 재정의해야합니다.
2. 자신의 클래스 내부에 있는 이니셜라이저를 사용하려면 편리한 이니셜라이저를 사용합니다.
3. 슈퍼클래스에 있는 이니셜라이저와 같은 매개변수를 받는 이니셜라이저를 쓰는데, 자신의 클래스 내부에 있는 이니셜라이저를 내부에서 사용하려면 오버라이드 + 편리한 이니셜라이저를 사용합니다.(1 + 2)
추가적으로 편리한 이니셜라이저 내부에서 슈퍼클래스의 이니셜라이저를 호출 할 수 없습니다!

실패 가능한 이니셜라이저

이니셜라이저는 이름이 따로 있는 것이 아니라 파라미터로 구분하기 때문에, 실패 가능한 이니셜라이저와 실패 불가능한 이니셜라이저를 같은 파라미터 타입과 이름으로 동시에 사용할 수 없습니다.

실패 가능한 이니셜라이저의 반환값으로 옵셔널 값을 생성합니다. 초기화에 실패하는 부분에서 nil을 반환하는 코드를 작성해 초기화가 실패했다는 것을 나타낼 수 있습니다.
(엄밀히 말하면 초기자 init은 값을 반환하지 않습니다. 그래서 비록 nil을 반환하는 return nil 코드에는 사용하지만 init이 성공하는 경우 return 키워드를 사용하지 않습니다.)

실패 가능한 이니셜라이저로 초기화되면, 성공하더라도 값은 옵셔널 이고,
같은 클래스에 실패불가능한 이니셜라이저가 있고, 그 이니셜라이저로 초기화하면 옵셔널이 아닙니다!

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"

let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// Prints "3.14159 conversion to Int does not maintain value"


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"

let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal

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

(빈것(Empty)과 nil은 다릅니다. 그래서 .isEmpty를 이용해 비교하지 않으면 인스턴스 초기화시 “”를 넣어도 초기화가 됩니다. 이것은 의도한게 아니므로, empty를 확인해서 nil을 반환하도록 구현해야 합니다.)

열거형에서 사용하는 실패 가능한 초기자

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."

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."

이니셜라이저 실패의 생성

실패 가능한 이니셜라이저에서 실패가 발생하면 즉시 관련된 이니셜라이저가 중단 됩니다.
실패가능한 초기자를 실패가 가능하지 않은 초기자에 위임할 수 있습니다. 이런 방식을 조합해 현재 존재하는 초기자를 특정 상황에만 실패하는 초기자로 만들 수 있습니다.

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

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("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")
}

//sub
//super
//sub end
//Item: sock, quantity: 2
//Unable to initialize zero shirts
//sub <- 자식클래스에서 중간에 멈추면서, 그 이후 코드는 실행되지 않음! 여기까지만 출력됨
//Unable to initialize one unnamed product

실패 가능한 이니셜라이저의 오버라이딩

또한 슈퍼클래스의 이니셜라이저가 실패하도록 구현되었더라도, 서브클래스에서는 실패하지 않도록 정의할 수 있습니다. (단, 실패할 수 없는 이니셜라이저를 실패하도록 구현할 수는 없습니다!)

class Document {
    var name: String?
    // this initializer creates a document with a nil name value
    init() {}
    // this initializer creates a document with a nonempty name value
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

/// init?을 init으로 override
class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

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

init!

실패 가능한 초기자 init?을 init!로 오버라이딩 할 수 있고 아니면 위임해서 사용할 수 있습니다.
옵셔널에서 ?와 !의 차이와 비슷한 느낌입니다.
반대로도 할 수 있겠지만, init!에 의해 실패할 가능성이 높습니다.

필수 이니셜라이저

필수 이니셜라이저가 있는 클래스를 상속 받은 후 이니셜라이저도 만들지 않으면 상관없지만, 만약 하나라도 이니셜라이저를 만들었다면 자식 클래스에서 필수 이니셜라이저를 명시 해줘야합니다.
따로 내용을 적지 않고 required 키워드만 붙이면, 그 이니셜라이저는 알아서 슈퍼클래스의 이니셜라이저를 호출합니다.(꼭 내용을 구현할 필요는 없음)

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

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

클로저나 함수를 이용해 기본 프로퍼티 값을 설정하기

class SomeClass {
    let someProperty: SomeType = {
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
    }()
}
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"

오늘도 스위프트 공식문서를 정리해보았군욥~
다음편도 힘내보겠습니다!

감사합니다🙇🏻‍♀️

profile
한달 차 iOS 개발자입니다🐥

0개의 댓글