Initialization

Groot·2022년 11월 6일
0

Swift Language Guide

목록 보기
24/24
post-thumbnail

Initialization

  • 초기화는 사용할 클래스, 구조 또는 열거형의 인스턴스를 준비하는 프로세스입니다.
  • 이 프로세스에는 해당 인스턴스의 각 저장 속성에 대한 초기 값을 설정하고 새 인스턴스를 사용할 준비가 되기 전에 필요한 다른 설정 또는 초기화를 수행하는 작업이 포함됩니다.
  • 특정 유형의 새 인스턴스를 생성하기 위해 호출할 수 있는 특수 메서드와 같은 초기화 프로그램을 정의하여 이 초기화 프로세스를 구현합니다.
  • Objective-C 이니셜라이저와 달리 Swift 이니셜라이저는 값을 반환하지 않습니다.
  • 주요 역할은 유형의 새 인스턴스가 처음 사용되기 전에 올바르게 초기화되도록 하는 것입니다.
  • 클래스 유형의 인스턴스는 해당 클래스의 인스턴스가 할당 해제되기 직전에 모든 사용자 지정 정리를 수행하는 초기화 해제기를 구현할 수도 있습니다.
  • 초기화 해제에 대한 자세한 내용은 초기화 해제를 참조하세요.

📌 Setting Initial Values for Stored Properties

  • 클래스와 구조체는 해당 클래스 또는 구조체의 인스턴스가 생성될 때까지 저장된 모든 속성을 적절한 초기 값으로 설정해야 합니다.
  • 저장된 속성은 불확실한 상태로 둘 수 없습니다.
  • 이니셜라이저 내에서 저장된 속성의 초기 값을 설정하거나 속성 정의의 일부로 기본 속성 값을 할당하여 설정할 수 있습니다.
  • 이러한 작업은 다음 섹션에서 설명합니다.

    저장된 속성에 기본값을 할당하거나 이니셜라이저 내에서 초기 값을 설정하면 속성 관찰자를 호출하지 않고 해당 속성의 값이 직접 설정됩니다.

📍 Initializers

  • 이니셜라이저는 특정 유형의 새 인스턴스를 생성하기 위해 호출됩니다.
  • 가장 단순한 형태의 초기화 프로그램은 매개변수가 없는 인스턴스 메소드와 같으며 init 키워드를 사용하여 작성됩니다.
    init() {
        // perform some initialization here
    }
  • 아래 예는 화씨 스케일로 표현된 온도를 저장하기 위해 화씨라는 새로운 구조를 정의합니다.
  • Fahrenheit 구조에는 Double 유형의 하나의 저장된 속성인 temperature가 있습니다.
    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"
  • 이 구조는 매개변수가 없는 단일 초기화인 init를 정의하며 저장된 온도를 32.0(화씨의 물의 어는점) 값으로 초기화합니다.

📍 Default Property Values

  • 위와 같이 이니셜라이저 내에서 저장된 속성의 초기 값을 설정할 수 있습니다.
  • 또는 속성 선언의 일부로 기본 속성 값을 지정합니다.
  • 속성이 정의될 때 속성에 초기 값을 할당하여 기본 속성 값을 지정합니다.

    속성이 항상 동일한 초기 값을 사용하는 경우 이니셜라이저 내에서 값을 설정하는 것보다 기본값을 제공하십시오.
    최종 결과는 동일하지만 기본값은 속성의 초기화를 선언과 더 밀접하게 연결합니다.
    더 짧고 명확한 이니셜라이저를 만들고 기본값에서 속성 유형을 유추할 수 있습니다.
    또한 이 장의 뒷부분에서 설명하는 것처럼 기본값을 사용하면 기본 이니셜라이저와 이니셜라이저 상속을 더 쉽게 활용할 수 있습니다.

  • 속성이 선언된 지점에서 온도 속성에 대한 기본값을 제공하여 위에서 화씨 구조를 더 간단한 형식으로 작성할 수 있습니다.
    struct Fahrenheit {
        var temperature = 32.0
    }

📌 Customizing Initialization

  • 다음 섹션에 설명된 대로 입력 매개변수 및 선택적 속성 유형을 사용하거나 초기화 중에 상수 속성을 할당하여 초기화 프로세스를 사용자 지정할 수 있습니다.

📍 Initialization Parameters

  • 초기화 매개변수를 초기화 정의의 일부로 제공하여 초기화 프로세스를 사용자 지정하는 값의 유형과 이름을 정의할 수 있습니다.
  • 초기화 매개변수는 기능 및 메소드 매개변수와 동일한 기능 및 구문을 갖습니다.
  • 다음 예제에서는 섭씨로 표시된 온도를 저장하는 섭씨라는 구조를 정의합니다.
  • 섭씨 구조는 init(fromFahrenheit:) 및 init(fromKelvin:)이라는 두 개의 사용자 정의 이니셜라이저를 구현합니다.
  • 이 초기화는 다른 온도 척도의 값으로 구조의 새 인스턴스를 초기화합니다.
    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
  • 첫 번째 이니셜라이저는 fromFahrenheit의 인수 레이블과 화씨의 매개변수 이름을 가진 단일 초기화 매개변수를 갖습니다.
  • 두 번째 이니셜라이저는 fromKelvin이라는 인수 레이블과 kelvin이라는 매개변수 이름을 가진 단일 초기화 매개변수를 갖습니다.
  • 두 이니셜라이저는 단일 인수를 해당하는 섭씨 값으로 변환하고 이 값을 temperatureInCelsius라는 속성에 저장합니다.

📍 Parameter Names and Argument Labels

  • 함수 및 메서드 매개변수와 마찬가지로 초기화 매개변수는 초기화 본체 내에서 사용할 매개변수 이름과 초기화 호출 시 사용할 인수 레이블을 모두 가질 수 있습니다.
  • 그러나 이니셜라이저는 함수와 메서드처럼 괄호 앞에 식별 함수 이름이 없습니다.
  • 따라서 이니셜라이저 매개변수의 이름과 유형은 호출해야 하는 이니셜라이저를 식별하는 데 특히 중요한 역할을 합니다.
  • 이 때문에 Swift는 초기화를 제공하지 않는 경우 초기화의 모든 매개변수에 대해 자동 인수 레이블을 제공합니다.
  • 다음 예제에서는 빨강, 녹색 및 파랑이라는 세 가지 상수 속성이 있는 Color라는 구조를 정의합니다.
  • 이러한 속성은 0.0에서 1.0 사이의 값을 저장하여 색상의 빨강, 녹색 및 파랑의 양을 나타냅니다.
  • Color는 빨강, 녹색 및 파랑 구성 요소에 대해 Double 유형의 적절하게 명명된 매개 변수 세 개를 초기화 프로그램에 제공합니다.
  • Color는 또한 세 가지 색상 구성 요소 모두에 동일한 값을 제공하는 데 사용되는 단일 흰색 매개변수가 있는 두 번째 이니셜라이저를 제공합니다.
    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
        }
    }
  • 두 초기화 모두 각 초기화 매개변수에 대해 명명된 값을 제공하여 새 Color 인스턴스를 생성하는 데 사용할 수 있습니다.
    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

📍 Initializer Parameters Without Argument Labels

  • 초기화 매개변수에 인수 레이블을 사용하지 않으려면 해당 매개변수에 대한 명시적 인수 레이블 대신 밑줄(_)을 작성하여 기본 동작을 재정의하십시오.
  • 다음은 위의 초기화 매개변수에 있는 섭씨 예제의 확장된 버전으로, 이미 섭씨 눈금에 있는 Double 값에서 새 섭씨 인스턴스를 생성하는 추가 이니셜라이저가 있습니다.
    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
  • 이니셜라이저 호출celsius(37.0)은 인수 레이블이 필요 없이 의도가 명확합니다.
  • 따라서 이 초기화 프로그램을 init(_ celsius: Double)로 작성하여 명명되지 않은 Double 값을 제공하여 호출할 수 있도록 하는 것이 적절합니다.

📍 Optional Property Types

  • 사용자 정의 유형에 "nil"이 논리적으로 허용되는 저장 속성이 있는 경우는, 아마도 초기화 중에 해당 값을 설정할 수 없기 때문일 수 있습니다.
  • 또는 나중에 "nil"이 허용되기 때문에 선택적 유형으로 속성을 선언하십시오.
  • 다음 예제에서는 response라는 선택적 String 속성을 사용하여 SurveyQuestion이라는 클래스를 정의합니다.
    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."
  • 설문조사 질문에 대한 응답은 질문을 받을 때까지 알 수 없으므로 응답 속성은 String? 또는 "Optional String" 유형으로 선언됩니다.
  • SurveyQuestion의 새 인스턴스가 초기화될 때 "아직 문자열이 없음"을 의미하는 nil의 기본값이 자동으로 할당됩니다.

📍 Assigning Constant Properties During Initialization

  • 초기화가 완료될 때까지 일정한 값으로 설정되어 있는 한 초기화 중 언제든지 상수 속성에 값을 할당할 수 있습니다.
  • 상수 속성에 값이 할당되면 더 이상 수정할 수 없습니다.

    클래스 인스턴스의 경우 상수 속성은 초기화하는 동안 해당 속성을 도입한 클래스에서만 수정할 수 있습니다. 하위 클래스에서 수정할 수 없습니다.

  • 질문의 텍스트 속성에 대해 변수 속성 대신 상수 속성을 사용하도록 위에서 SurveyQuestion 예제를 수정하여 SurveyQuestion의 인스턴스가 생성된 후에는 질문이 변경되지 않음을 나타낼 수 있습니다.
  • text 속성은 이제 상수이지만 여전히 클래스의 이니셜라이저 내에서 설정할 수 있습니다.
    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

  • Swift는 모든 속성에 대한 기본값을 제공하고 하나 이상의 초기화 자체를 제공하지 않는 모든 구조 또는 클래스에 대한 기본 초기화를 제공합니다.
  • 기본 이니셜라이저는 모든 속성이 기본값으로 설정된 새 인스턴스를 생성합니다.
  • 이 예에서는 쇼핑 목록에 있는 항목의 이름, 수량 및 구매 상태를 캡슐화하는 ShoppingListItem이라는 클래스를 정의합니다.
    class ShoppingListItem {
        var name: String?
        var quantity = 1
        var purchased = false
    }
    var item = ShoppingListItem()
  • ShoppingListItem 클래스의 모든 속성에는 기본값이 있고, 슈퍼클래스가 없는 기본 클래스이기 때문에 ShoppingListItem은 모든 속성이 기본값으로 설정된 새 인스턴스를 생성하는 기본 이니셜라이저 구현을 자동으로 얻습니다.
  • (name 속성은 선택적 String 속성이므로 이 값이 코드에 작성되지 않아도 기본값 nil을 자동으로 받습니다.)
  • 위의 예는 ShoppingListItem 클래스의 기본 이니셜라이저를 사용하여 이니셜라이저 구문으로 클래스의 새 인스턴스를 만들고 ShoppingListItem()으로 작성된 이 새 인스턴스를 item이라는 변수에 할당합니다.

📍 Memberwise Initializers for Structure Types

  • 구조체 유형은 자체 사용자 정의 이니셜라이저를 정의하지 않으면 자동으로 멤버별 이니셜라이저를 받습니다.

  • 기본 이니셜라이저와 달리 구조체는 기본값이 없는 저장된 속성이 있더라도 멤버별 이니셜라이저를 받습니다.

  • 멤버별 이니셜라이저는 새 구조체 인스턴스의 멤버 속성을 초기화하는 약식 방법입니다.

  • 새 인스턴스의 속성에 대한 초기 값은 이름으로 멤버별 이니셜라이저에 전달할 수 있습니다.

  • 아래 예제는 너비와 높이라는 두 가지 속성을 가진 Size라는 구조를 정의합니다.

  • 두 속성 모두 기본값 0.0을 할당하여 Double 유형으로 유추됩니다.

  • Size 구조체는 새 Size 인스턴스를 초기화하는 데 사용할 수 있는 init(width:height:) 멤버별 이니셜라이저를 자동으로 받습니다.

    struct Size {
        var width = 0.0, height = 0.0
    }
    let twoByTwo = Size(width: 2.0, height: 2.0)
  • 멤버별 이니셜라이저를 호출할 때 기본값이 있는 모든 속성의 값을 생략할 수 있습니다.

  • 위의 예에서 Size 구조는 height 및 width 속성 모두에 대한 기본값을 갖습니다.

  • 속성 중 하나 또는 두 속성을 모두 생략할 수 있으며 이니셜라이저는 생략된 모든 항목에 대해 기본값을 사용합니다.

    let zeroByTwo = Size(height: 2.0)
    print(zeroByTwo.width, zeroByTwo.height)
    // Prints "0.0 2.0"
    
    let zeroByZero = Size()
    print(zeroByZero.width, zeroByZero.height)
    // Prints "0.0 0.0"

📌 Initializer Delegation for Value Types

  • 이니셜라이저는 다른 이니셜라이저를 호출하여 인스턴스 초기화의 일부를 수행할 수 있습니다.
  • 이니셜라이저 위임이라고 하는 이 프로세스는 여러 이니셜라이저에서 코드가 중복되는 것을 방지합니다.
  • 초기화 위임이 작동하는 방식과 허용되는 위임 형식에 대한 규칙은 값 유형과 클래스 유형에 따라 다릅니다.
  • 값 유형(구조 및 열거형)은 상속을 지원하지 않으므로 해당 초기화자 위임 프로세스는 자신이 제공하는 다른 초기화자에게만 위임할 수 있기 때문에 상대적으로 간단합니다.
  • 그러나 클래스는 상속에 설명된 대로 다른 클래스에서 상속할 수 있습니다.
  • 이는 클래스가 상속하는 모든 저장 속성에 초기화 중에 적절한 값이 할당되도록 하는 추가 책임이 있음을 의미합니다.
  • 이러한 책임은 아래의 클래스 상속 및 초기화에 설명되어 있습니다.
  • 값 유형의 경우 자체 사용자 정의 이니셜라이저를 작성할 때 self.init를 사용하여 동일한 값 유형의 다른 이니셜라이저를 참조합니다.
  • 이니셜라이저 내에서만 self.init를 호출할 수 있습니다.
  • 값 유형에 대한 사용자 정의 이니셜라이저를 정의하면 해당 유형에 대한 기본 이니셜라이저(또는 구조체인 경우 멤버별 이니셜라이저)에 더 이상 액세스할 수 없습니다.
  • 이 제약 조건은 더 복잡한 이니셜라이저에 제공된 추가 필수 설정이 자동 이니셜라이저 중 하나를 사용하는 사람에 의해 우발적으로 우회되는 상황을 방지합니다.

    기본 이니셜라이저와 멤버별 이니셜라이저와 사용자 정의 이니셜라이저를 사용하여 사용자 정의 값 유형을 초기화할 수 있도록 하려면 값 유형의 원래 구현의 일부가 아닌 확장에 사용자 정의 이니셜라이저를 작성하십시오. 자세한 내용은 확장을 참조하십시오.

  • 다음 예제에서는 기하학적 사각형을 나타내는 사용자 지정 Rect 구조를 정의합니다. 이 예제에는 Size 및 Point라는 두 가지 지원 구조가 필요하며, 둘 다 모든 속성에 대해 기본값 0.0을 제공합니다.
    struct Size {
        var width = 0.0, height = 0.0
    }
    struct Point {
        var x = 0.0, y = 0.0
    }
  • 아래의 Rect 구조는 0으로 초기화되는 기본 origin 및 size 속성 값을 사용하거나, 특정 원점과 크기를 제공하거나, 특정 중심점과 크기를 제공하는 세 가지 방법 중 하나로 초기화할 수 있습니다.
  • 이러한 초기화 옵션은 Rect 구조 정의의 일부인 세 가지 사용자 정의 이니셜라이저로 표시됩니다.
    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)
        }
    }
  • 첫 번째 Rect 이니셜라이저인 init()는 자체 사용자 정의 이니셜라이저가 없는 경우 구조가 수신했을 기본 이니셜라이저와 기능적으로 동일합니다.
  • 이 이니셜라이저는 중괄호 {}의 빈 쌍으로 표시되는 빈 본문을 가지고 있습니다.
  • 이 이니셜라이저를 호출하면 원점 및 크기 속성이 속성 정의에서 Point(x: 0.0, y: 0.0) 및 Size(width: 0.0, height: 0.0)의 기본값으로 초기화되는 Rect 인스턴스를 반환합니다.
    let basicRect = Rect()
    // basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
  • 두 번째 Rect 이니셜라이저인 init(origin:size:)는 자체 사용자 정의 이니셜라이저가 없는 경우 구조가 수신했을 멤버별 이니셜라이저와 기능적으로 동일합니다.
  • 이 이니셜라이저는 단순히 origin 및 size 인수 값을 적절한 저장된 속성에 할당합니다.
    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)
  • 세 번째 Rect 이니셜라이저인 init(center:size:)는 약간 더 복잡합니다. 중심점과 크기 값을 기준으로 적절한 원점을 계산하는 것부터 시작합니다.
  • 그런 다음 적절한 속성에 새 원점 및 크기 값을 저장하는 init(origin:size:) 이니셜라이저를 호출(또는 위임)합니다.
    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:size:) 이니셜라이저는 적절한 속성 자체에 원점 및 크기의 새 값을 할당할 수 있습니다.
  • 그러나 이미 해당 기능을 정확히 제공하는 기존 이니셜라이저를 활용하는 것이 더 편리하고 의도가 더 명확합니다.

    init() 및 init(origin:size:) 이니셜라이저를 직접 정의하지 않고 이 예제를 작성하는 다른 방법은 확장을 참조하세요.

📌 Class Inheritance and Initialization

  • 클래스의 모든 저장된 속성(클래스가 상위 클래스에서 상속하는 속성 포함)은 초기화 중에 초기 값이 할당되어야 합니다.
  • Swift는 모든 저장된 속성이 초기 값을 받도록 하기 위해 클래스 유형에 대해 두 가지 종류의 이니셜라이저를 정의합니다.
  • 이들은 지정 이니셜라이저 및 편의 이니셜라이저로 알려져 있습니다.

📍 Designated Initializers and Convenience Initializers

  • 지정된 이니셜라이저는 클래스의 기본 이니셜라이저입니다.
  • 지정된 이니셜라이저는 해당 클래스에 의해 도입된 모든 속성을 완전히 초기화하고 적절한 수퍼클래스 이니셜라이저를 호출하여 수퍼클래스 체인까지 초기화 프로세스를 계속합니다.
  • 클래스는 지정된 이니셜라이저가 거의 없는 경향이 있으며 클래스가 하나만 갖는 것이 일반적입니다.
  • 지정된 이니셜라이저는 초기화가 발생하고 초기화 프로세스가 수퍼클래스 체인까지 계속되는 "퍼널" 지점입니다.
  • 모든 클래스에는 지정된 초기화 프로그램이 하나 이상 있어야 합니다.
  • 어떤 경우에는 이 요구 사항이 아래의 자동 이니셜라이저 상속에 설명된 대로 수퍼클래스에서 하나 이상의 지정된 이니셜라이저를 상속함으로써 충족됩니다.
  • 편의 이니셜라이저는 클래스에 대한 이니셜라이저를 지원하는 보조 이니셜라이저입니다.
  • 지정된 이니셜라이저의 매개변수 중 일부를 기본값으로 설정하여 편의 이니셜라이저와 동일한 클래스에서 지정된 이니셜라이저를 호출하도록 편의 이니셜라이저를 정의할 수 있습니다.
  • 또한 편의 이니셜라이저를 정의하여 특정 사용 사례 또는 입력 값 유형에 대한 해당 클래스의 인스턴스를 생성할 수 있습니다.
  • 클래스에서 필요하지 않은 경우 편의 이니셜라이저를 제공할 필요가 없습니다.
  • 일반적인 초기화 패턴에 대한 바로 가기가 시간을 절약할 때나, 클래스 초기화 의도를 더 명확하게 만들 때 편의 이니셜라이저를 만듭니다.

📍 Syntax for Designated and Convenience Initializers

  • 클래스에 대한 지정 이니셜라이저는 값 유형에 대한 단순 이니셜라이저와 동일한 방식으로 작성됩니다.

    init(parameters) {
    statements
    }

  • Convenience 이니셜라이저는 동일한 스타일로 작성되지만, init 키워드 앞에 공백으로 구분된 편의 한정자가 배치됩니다.

📍 Initializer Delegation for Class Types

  • 지정 이니셜라이저와 편의 이니셜라이저 간의 관계를 단순화하기 위해 Swift는 이니셜라이저 간의 위임 호출에 대해 다음 세 가지 규칙을 적용합니다.
  • 규칙 1
    • 지정된 이니셜라이저는 직계 슈퍼클래스에서 지정된 이니셜라이저를 호출해야 합니다.
  • 규칙 2
    • 편의 이니셜라이저는 동일한 클래스의 다른 이니셜라이저를 호출해야 합니다.
  • 규칙 3
    • 편의 이니셜라이저는 궁극적으로 지정된 이니셜라이저를 호출해야 합니다.
  • 이것을 기억하는 간단한 방법은 다음과 같습니다.
    • 지정된 이니셜라이저는 항상 위임해야 합니다.
    • 편의 이니셜라이저는 항상 위임해야 합니다.
  • 이러한 규칙은 아래 그림에 설명되어 있습니다.
  • 여기에서 슈퍼클래스는 하나의 지정된 이니셜라이저와 2개의 편의 이니셜라이저를 가지고 있습니다.
  • 하나의 편의 이니셜라이저는 다른 편의 이니셜라이저를 호출하고, 이는 차례로 단일 지정된 이니셜라이저를 호출합니다.
  • 이 그림의 서브클래스에는 두 개의 지정된 이니셜라이저와 하나의 편의 이니셜라이저가 있습니다.
  • 편의 이니셜라이저는 동일한 클래스의 다른 이니셜라이저만 호출할 수 있으므로 두 개의 지정된 이니셜라이저 중 하나를 호출해야 합니다.
  • 이것은 위의 규칙 2와 3을 충족합니다.
  • 수퍼클래스 자체에는 추가 수퍼클래스가 없으므로 규칙 1이 적용되지 않습니다.

    이러한 규칙은 클래스의 사용자가 각 클래스의 인스턴스를 만드는 방법에 영향을 주지 않습니다.
    위 다이어그램의 모든 이니셜라이저는 속한 클래스의 완전히 초기화된 인스턴스를 만드는 데 사용할 수 있습니다.
    규칙은 클래스의 이니셜라이저 구현을 작성하는 방법에만 영향을 미칩니다.

  • 아래 그림은 4개의 클래스에 대한 보다 복잡한 클래스 계층 구조를 보여줍니다. 이 계층에서 지정된 이니셜라이저가 클래스 초기화를 위한 "깔때기" 지점으로 작동하여 체인의 클래스 간의 상호 관계를 단순화하는 방법을 보여줍니다.

📍 Two-Phase Initialization

  • Swift의 클래스 초기화는 2단계 프로세스입니다.
  • 첫 번째 단계에서 각 저장된 속성은 이를 도입한 클래스에 의해 초기 값이 할당됩니다.
  • 모든 저장된 속성의 초기 상태가 결정되면 두 번째 단계가 시작되고 새 인스턴스가 사용할 준비가 된 것으로 간주되기 전에 각 클래스에 저장된 속성을 추가로 사용자 지정할 수 있는 기회가 주어집니다.
  • 2단계 초기화 프로세스를 사용하면 초기화를 안전하게 수행하는 동시에 클래스 계층 구조의 각 클래스에 완전한 유연성을 제공합니다.
  • 2단계 초기화는 속성 값이 초기화되기 전에 액세스되는 것을 방지하고 속성 값이 다른 이니셜라이저에 의해 예기치 않게 다른 값으로 설정되는 것을 방지합니다.

    Swift의 2단계 초기화 프로세스는 Objective-C의 초기화와 유사합니다.
    주요 차이점은 1단계에서 Objective-C가 모든 속성에 0 또는 null 값(예: 0 또는 nil)을 할당한다는 것입니다.
    Swift의 초기화 흐름은 사용자 정의 초기 값을 설정할 수 있고 0 또는 nil이 유효한 기본값이 아닌 유형에 대처할 수 있다는 점에서 더 유연합니다.

  • Swift의 컴파일러는 2단계 초기화가 오류 없이 완료되었는지 확인하기 위해 4가지 유용한 안전 검사를 수행합니다.
  • 안전 점검 1
    • 지정된 이니셜라이저는 상위 클래스 이니셜라이저에 위임하기 전에 해당 클래스에 의해 도입된 모든 속성이 초기화되었는지 확인해야 합니다.
  • 위에서 언급했듯이 객체에 대한 메모리는 저장된 모든 속성의 초기 상태가 알려진 후에만 완전히 초기화된 것으로 간주됩니다.
  • 이 규칙이 충족되기 위해서는 지정된 이니셜라이저가 체인을 넘겨주기 전에 자신의 모든 속성이 초기화되었는지 확인해야 합니다.
  • 안전 점검 2
    • 지정된 이니셜라이저는 상속된 속성에 값을 할당하기 전에 상위 클래스 이니셜라이저까지 위임해야 합니다.
    • 그렇지 않은 경우 지정된 초기화 프로그램이 할당한 새 값은 자체 초기화의 일부로 슈퍼클래스가 덮어씁니다.
  • 안전 점검 3
    • 편의 이니셜라이저는 속성(동일한 클래스에서 정의한 속성 포함)에 값을 할당하기 전에 다른 이니셜라이저에 위임해야 합니다.
    • 그렇지 않은 경우 편의 이니셜라이저가 할당하는 새 값은 자체 클래스의 지정된 이니셜라이저가 덮어씁니다.
  • 안전 점검 4
    • 초기화는 초기화의 첫 번째 단계가 완료될 때까지 인스턴스 메서드를 호출하거나 인스턴스 속성의 값을 읽거나 self를 값으로 참조할 수 없습니다.
  • 클래스 인스턴스는 첫 번째 단계가 끝날 때까지 완전히 유효하지 않습니다. 속성은 액세스만 가능하고 메서드는 첫 번째 단계가 끝날 때 클래스 인스턴스가 유효한 것으로 확인된 후에만 호출할 수 있습니다.
  • 위의 네 가지 안전 검사를 기반으로 2단계 초기화가 실행되는 방식은 다음과 같습니다.
  • 1단계
    • 클래스에서 지정 또는 편의 이니셜라이저가 호출됩니다.
    • 해당 클래스의 새 인스턴스에 대한 메모리가 할당됩니다. 메모리가 아직 초기화되지 않았습니다.
    • 해당 클래스의 지정된 이니셜라이저는 해당 클래스에 의해 도입된 모든 저장된 속성에 값이 있음을 확인합니다. 이제 이러한 저장된 속성에 대한 메모리가 초기화됩니다.
    • 지정된 이니셜라이저는 자신의 저장된 속성에 대해 동일한 작업을 수행하기 위해 수퍼클래스 이니셜라이저에 전달합니다.
    • 이것은 체인의 맨 위에 도달할 때까지 클래스 상속 체인을 계속합니다.
    • 체인의 맨 위에 도달하고 체인의 마지막 클래스가 저장된 모든 속성에 값이 있음을 확인하면 인스턴스의 메모리가 완전히 초기화된 것으로 간주되고 1단계가 완료됩니다.
  • 2단계
    • 해당 클래스의 지정된 이니셜라이저는 해당 클래스에 의해 도입된 모든 저장된 속성에 값이 있음을 확인합니다. 이제 이러한 저장된 속성에 대한 메모리가 초기화됩니다.
    • 마지막으로, 체인의 모든 편의 이니셜라이저는 인스턴스를 사용자 정의하고 자체 작업을 수행할 수 있는 옵션이 있습니다.
  • 다음은 1단계에서 가상의 하위 클래스와 상위 클래스에 대한 초기화 호출을 찾는 방법입니다.
  • 이 예제에서 초기화는 서브클래스의 편의 이니셜라이저를 호출하는 것으로 시작됩니다.
  • 이 편의 이니셜라이저는 아직 속성을 수정할 수 없습니다.
  • 동일한 클래스의 지정된 이니셜라이저에 위임합니다.
  • 지정된 이니셜라이저는 안전 검사 1에 따라 모든 하위 클래스 속성에 값이 있는지 확인합니다.
  • 그런 다음 상위 클래스에서 지정된 이니셜라이저를 호출하여 체인 위로 초기화를 계속합니다.
  • 슈퍼클래스의 지정된 이니셜라이저는 모든 슈퍼클래스 속성에 값이 있는지 확인합니다.
  • 더 이상 초기화할 수퍼클래스가 없으므로 더 이상의 위임이 필요하지 않습니다.
  • 슈퍼클래스의 모든 속성이 초기값을 가지면 해당 메모리는 완전히 초기화된 것으로 간주되고 1단계가 완료됩니다.
  • 2단계에서 동일한 초기화 호출을 찾는 방법은 다음과 같습니다.
  • 슈퍼클래스의 지정된 이니셜라이저는 이제 인스턴스를 추가로 커스터마이징할 수 있는 기회가 있습니다(꼭 그럴 필요는 없지만).
  • 슈퍼클래스의 지정된 이니셜라이저가 완료되면 서브클래스의 지정된 이니셜라이저가 추가 사용자 지정을 수행할 수 있습니다(반드시 그렇지 않아도 됩니다).
  • 마지막으로 서브클래스의 지정된 이니셜라이저가 완료되면 원래 호출된 편의 이니셜라이저가 추가 사용자 정의를 수행할 수 있습니다.

📍 Initializer Inheritance and Overriding

  • Objective-C의 서브클래스와 달리 Swift 서브클래스는 기본적으로 슈퍼클래스 이니셜라이저를 상속하지 않습니다.
  • Swift의 접근 방식은 수퍼 클래스의 간단한 초기화가 더 전문화된 하위 클래스에 상속되고 완전히 또는 올바르게 초기화되지 않은 하위 클래스의 새 인스턴스를 만드는 데 사용되는 상황을 방지합니다.

    슈퍼클래스 이니셜라이저는 특정 상황에서 상속되지만 그렇게 하는 것이 안전하고 적절한 경우에만 상속됩니다. 자세한 내용은 아래의 자동 초기화 프로그램 상속을 참조하세요.

  • 사용자 정의 하위 클래스가 상위 클래스와 동일한 이니셜라이저 중 하나 이상을 제공하도록 하려면 하위 클래스 내에서 해당 이니셜라이저의 사용자 정의 구현을 제공할 수 있습니다.
  • 슈퍼클래스 지정 이니셜라이저와 일치하는 서브클래스 이니셜라이저를 작성할 때 해당 지정된 이니셜라이저의 재정의를 효과적으로 제공하는 것입니다.
  • 따라서 서브클래스의 이니셜라이저 정의 전에 override 수식어를 작성해야 합니다.
  • 기본 이니셜라이저에 설명된 대로 자동으로 제공된 기본 이니셜라이저를 재정의하는 경우에도 마찬가지입니다.
  • 재정의된 속성, 메서드 또는 하위 첨자에서와 마찬가지로 재정의 수정자가 있으면 Swift에서 수퍼클래스에 재정의할 지정된 이니셜라이저가 일치하는지 확인하라는 메시지가 표시됩니다.
  • 재정의 이니셜라이저에 대한 매개변수가 의도한 대로 지정되었는지 확인합니다.

    서브클래스의 이니셜라이저 구현이 편의 이니셜라이저인 경우에도 수퍼클래스 지정 이니셜라이저를 재정의할 때 항상 override 수정자를 작성합니다.

  • 반대로 슈퍼클래스 편의 이니셜라이저와 일치하는 서브클래스 이니셜라이저를 작성하면, 상위 클래스 편의 이니셜라이저는 위에서 클래스 유형에 대한 이니셜라이저 위임에서 설명한 규칙에 따라 서브클래스에서 직접 호출할 수 없습니다.
  • 따라서 하위 클래스는 (엄밀히 말하면) 상위 클래스 이니셜라이저의 재정의를 제공하지 않습니다.
  • 결과적으로 슈퍼클래스 편의 이니셜라이저의 일치하는 구현을 제공할 때 override 수정자를 작성하지 않습니다.
  • 아래 예제에서는 Vehicle이라는 기본 클래스를 정의합니다.
  • 이 기본 클래스는 기본 Int 값이 0인 numberOfWheels라는 저장 속성을 선언합니다.
  • numberOfWheels 속성은 description이라는 계산 속성에서 차량 특성에 대한 문자열 설명을 만드는 데 사용됩니다.
    class Vehicle {
        var numberOfWheels = 0
        var description: String {
            return "\(numberOfWheels) wheel(s)"
        }
    }
  • Vehicle 클래스는 유일한 저장된 속성에 대한 기본값을 제공하며 사용자 정의 이니셜라이저 자체를 제공하지 않습니다.
  • 결과적으로 기본 이니셜라이저에 설명된 대로 기본 이니셜라이저를 자동으로 받습니다.
  • 기본 이니셜라이저(사용 가능한 경우)는 항상 클래스에 대해 지정된 이니셜라이저이며 numberOfWheels가 0인 새 Vehicle 인스턴스를 만드는 데 사용할 수 있습니다.
    let vehicle = Vehicle()
    print("Vehicle: \(vehicle.description)")
    // Vehicle: 0 wheel(s)
  • 다음 예제는 Bicycle이라는 Vehicle의 서브클래스를 정의합니다
    class Bicycle: Vehicle {
        override init() {
            super.init()
            numberOfWheels = 2
        }
    }
  • Bicycle 서브클래스는 사용자 지정 지정 이니셜라이저 init()를 정의합니다.
  • 이 지정된 이니셜라이저는 Bicycle 슈퍼클래스의 지정된 이니셜라이저와 일치하므로 이 이니셜라이저의 Bicycle 버전은 재정의 수정자로 표시됩니다.
  • Bicycle에 대한 init() 이니셜라이저는 Bicycle 클래스의 슈퍼클래스인 Vehicle에 대한 기본 이니셜라이저를 호출하는 super.init()를 호출하여 시작합니다.
  • 이렇게 하면 Bicycle이 속성을 수정할 기회가 생기기 전에 numberOfWheels 상속된 속성이 Vehicle에 의해 초기화됩니다.
  • super.init()를 호출한 후 numberOfWheels의 원래 값은 새 값 2로 대체됩니다
  • Bicycle의 인스턴스를 생성하는 경우 상속된 설명 계산 속성을 호출하여 numberOfWheels 속성이 어떻게 업데이트되었는지 확인할 수 있습니다.
    let bicycle = Bicycle()
    print("Bicycle: \(bicycle.description)")
    // Bicycle: 2 wheel(s)
  • 서브클래스 이니셜라이저가 초기화 프로세스의 2단계에서 사용자 정의를 수행하지 않고 수퍼클래스에 동기식, 인수가 없는 지정 이니셜라이저가 있는 경우,모든 하위 클래스의 저장된 속성에 값을 할당한 후 super.init() 호출을 생략할 수 있습니다.
  • 슈퍼클래스의 초기화가 비동기적이라면 명시적으로 await super.init()를 작성해야 합니다.
  • 이 예제는 Hoverboard라고 하는 Vehicle의 또 다른 하위 클래스를 정의합니다.
  • 이니셜라이저에서 Hoverboard 클래스는 color 속성만 설정합니다.
  • 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)"
        }
    }
  • Hoverboard의 인스턴스는 Vehicle 이니셜라이저에서 제공하는 기본 휠 수를 사용합니다.
    let hoverboard = Hoverboard(color: "silver")
    print("Hoverboard: \(hoverboard.description)")
    // Hoverboard: 0 wheel(s) in a beautiful silver

    서브클래스는 초기화 중에 상속된 변수 속성을 수정할 수 있지만 상속된 상수 속성은 수정할 수 없습니다.

  • 위에서 언급했듯이 하위 클래스는 기본적으로 상위 클래스 이니셜라이저를 상속하지 않습니다.
  • 그러나 특정 조건이 충족되면 슈퍼클래스 이니셜라이저가 자동으로 상속됩니다.
  • 실제로 이것은 많은 일반적인 시나리오에서 이니셜라이저 재정의를 작성할 필요가 없으며 그렇게 하는 것이 안전할 때마다 최소한의 노력으로 슈퍼클래스 이니셜라이저를 상속할 수 있음을 의미합니다.
  • 하위 클래스에 도입하는 모든 새 속성에 대한 기본값을 제공한다고 가정하면 다음 두 가지 규칙이 적용됩니다.
  • 규칙 1
    • 서브클래스가 지정된 이니셜라이저를 정의하지 않으면 자동으로 모든 슈퍼클래스 지정 이니셜라이저를 상속합니다.
  • 규칙 2
    • 하위 클래스가 모든 상위 클래스 지정 이니셜라이저의 구현을 제공하는 경우(규칙 1에 따라 상속하여), 또는 정의의 일부로 사용자 정의 구현을 제공하면 자동으로 모든 슈퍼클래스 편의 이니셜라이저를 상속합니다.
  • 이러한 규칙은 하위 클래스가 편의 이니셜라이저를 추가하는 경우에도 적용됩니다.

    하위 클래스는 규칙 2를 충족하는 일부로 하위 클래스 편의 초기화로 상위 클래스 지정 초기화를 구현할 수 있습니다.

📍 Designated and Convenience Initializers in Action

  • 다음 예제에서는 지정된 이니셜라이저, 편의 이니셜라이저 및 자동 이니셜라이저 상속이 작동하는 모습을 보여줍니다.
  • 이 예제는 Food, RecipeIngredient 및 ShoppingListItem이라는 세 가지 클래스의 계층 구조를 정의하고 해당 이니셜라이저가 상호 작용하는 방식을 보여줍니다.
  • 계층 구조의 기본 클래스는 Food라고 하며, 이는 식품의 이름을 캡슐화하는 간단한 클래스입니다.
  • Food 클래스는 name이라는 단일 String 속성을 도입하고 Food 인스턴스를 생성하기 위한 두 개의 이니셜라이저를 제공합니다.
    class Food {
        var name: String
        init(name: String) {
            self.name = name
        }
        convenience init() {
            self.init(name: "[Unnamed]")
        }
    }
  • 아래 그림은 Food 클래스의 이니셜라이저 체인을 보여줍니다.
  • 클래스에는 기본 멤버별 이니셜라이저가 없으므로 Food 클래스는 name이라는 단일 인수를 사용하는 지정된 이니셜라이저를 제공합니다.
  • 이 이니셜라이저는 특정 이름을 가진 새 Food 인스턴스를 만드는 데 사용할 수 있습니다.
    let namedMeat = Food(name: "Bacon")
    // namedMeat's name is "Bacon"
  • Food 클래스의 init(name: String) 이니셜라이저는 지정된 이니셜라이저로 제공됩니다. 새로운 Food 인스턴스의 모든 저장된 속성이 완전히 초기화되도록 하기 때문입니다.
  • Food 클래스에는 슈퍼클래스가 없으므로 init(name: String) 이니셜라이저는 초기화를 완료하기 위해 super.init()를 호출할 필요가 없습니다.
  • Food 클래스는 또한 인수 없이 편리한 초기화 프로그램 init()을 제공합니다.
  • init() 초기화 프로그램은 이름 값이 [Unnamed]인 Food 클래스의 init(name: String)에 위임하여 새 음식에 대한 기본 자리 표시자 이름을 제공합니다.
    let mysteryMeat = Food()
    // mysteryMeat's name is "[Unnamed]"
  • 계층 구조의 두 번째 클래스는 RecipeIngredient라는 Food의 하위 클래스입니다.
  • RecipeIngredient 클래스는 요리 레시피의 재료를 모델링합니다.
  • 그것은 (Food에서 상속한 name 속성에 추가하여)quantity라는 Int 속성을 도입하고 RecipeIngredient 인스턴스를 생성하기 위한 두 개의 초기화를 정의합니다.
    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 클래스의 이니셜라이저 체인을 보여줍니다.
  • RecipeIngredient 클래스에는 새로운 RecipeIngredient 인스턴스의 모든 속성을 채우는 데 사용할 수 있는 단일 지정 초기화인 init(name: String, quantity: Int)가 있습니다.
  • 이 이니셜라이저는 RecipeIngredient에 의해 도입된 유일한 새 속성인 수량 속성에 전달된 수량 인수를 할당하는 것으로 시작합니다.
  • 그렇게 한 후, 초기화자는 Food 클래스의 init(name: String) 초기화자까지 위임합니다.
  • 위의 2단계 초기화의 안전점검 1을 만족하는 과정이다.
  • RecipeIngredient는 또한 이름만으로 RecipeIngredient 인스턴스를 생성하는 데 사용되는 편리한 초기화 프로그램 init(name: String)을 정의합니다.
  • 이 편의 이니셜라이저는 명시적 수량 없이 생성된 모든 RecipeIngredient 인스턴스에 대해 수량 1을 가정합니다.
  • 이 편의 이니셜라이저의 정의는 RecipeIngredient 인스턴스를 더 빠르고 더 편리하게 생성할 수 있도록 하고 여러 단일 수량 RecipeIngredient 인스턴스를 생성할 때 코드 중복을 방지합니다.
  • 이 편리한 이니셜라이저는 단순히 클래스의 지정된 이니셜라이저에 위임하여 수량 값 1을 전달합니다.
  • RecipeIngredient에서 제공하는 init(name: String) 편의 이니셜라이저는 Food의 init(name: String) 지정 이니셜라이저와 동일한 매개변수를 사용합니다.
  • 이 편리한 이니셜라이저는 상위 클래스에서 지정된 이니셜라이저를 재정의하므로 재정의 수정자로 표시해야 합니다(초기화 상속 및 재정의에서 설명한 대로).
  • RecipeIngredient는 init(name: String) 이니셜라이저를 편의 이니셜라이저로 제공하지만, RecipeIngredient는 수퍼클래스의 지정된 이니셜라이저 모두의 구현을 제공했습니다.
  • 따라서 RecipeIngredient는 상위 클래스의 모든 편의 이니셜라이저도 자동으로 상속합니다.
  • RecipeIngredient는 init(name: String) 이니셜라이저를 편의 이니셜라이저로 제공하지만, RecipeIngredient는 수퍼클래스의 지정된 이니셜라이저 모두의 구현을 제공했습니다.
  • 따라서 RecipeIngredient는 상위 클래스의 모든 편의 이니셜라이저도 자동으로 상속합니다.
  • 이 예에서 RecipeIngredient의 슈퍼클래스는 Food이며, 여기에는 init()라는 단일 편의 이니셜라이저가 있습니다.
  • 따라서 이 이니셜라이저는 RecipeIngredient에 상속됩니다.
  • init()의 상속된 버전은 Food 버전이 아닌 RecipeIngredient 버전의 init(name: String)에 위임한다는 점을 제외하면 Food 버전과 정확히 같은 방식으로 작동합니다.
  • 이 세 가지 초기화 프로그램 모두를 사용하여 새 RecipeIngredient 인스턴스를 만들 수 있습니다.
    let oneMysteryItem = RecipeIngredient()
    let oneBacon = RecipeIngredient(name: "Bacon")
    let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
  • 마지막 클래스는 ShoppingListItem이라는 RecipeIngredient의 하위 클래스입니다. ShoppingListItem 클래스는 쇼핑 목록에 나타나는 레시피 성분을 모델링합니다.
  • 쇼핑 목록의 모든 항목은 "미구매"로 시작합니다. 이 사실을 나타내기 위해 ShoppingListItem은 기본값이 false인 buy라는 부울 속성을 도입합니다.
  • ShoppingListItem은 또한 ShoppingListItem 인스턴스의 텍스트 설명을 제공하는 계산된 설명 속성을 추가합니다.
    class ShoppingListItem: RecipeIngredient {
        var purchased = false
        var description: String {
            var output = "\(quantity) x \(name)"
            output += purchased ? " ✔" : " ✘"
            return output
        }
    }

    ShoppingListItem은 구매에 대한 초기 값을 제공하기 위해 이니셜라이저를 정의하지 않습니다. 쇼핑 목록의 항목(여기에서 모델링됨)은 항상 구매하지 않은 상태로 시작하기 때문입니다.

  • 도입하는 모든 속성에 대한 기본값을 제공하고 이니셜라이저 자체를 정의하지 않기 때문에 ShoppingListItem은 상위 클래스에서 지정된 모든 이니셜라이저와 편리한 이니셜라이저를 자동으로 상속합니다.
  • 상속된 세 가지 초기화 프로그램을 모두 사용하여 새 ShoppingListItem 인스턴스를 만들 수 있습니다.
    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 ✘
  • 여기에서 세 개의 새 ShoppingListItem 인스턴스를 포함하는 배열 리터럴에서 BreakfastList라는 새 배열이 생성됩니다.
  • 배열의 유형은 [ShoppingListItem]으로 유추됩니다. 배열이 생성되면 배열 시작 부분의 ShoppingListItem 이름이 "[Unnamed]"에서 "Orange juice"로 변경되고 구매한 것으로 표시됩니다.
  • 배열의 각 항목에 대한 설명을 인쇄하면 기본 상태가 예상대로 설정되었음을 알 수 있습니다.

📌 Failable Initializers

  • 초기화가 실패할 수 있는 클래스, 구조 또는 열거형을 정의하는 것이 때때로 유용합니다.

  • 이 실패는 잘못된 초기화 매개변수 값, 필요한 외부 리소스의 부재 또는 초기화 성공을 방해하는 기타 조건으로 인해 트리거될 수 있습니다.

  • 실패할 수 있는 초기화 조건에 대처하려면 하나 이상의 실패 가능한 이니셜라이저를 클래스, 구조 또는 열거형 정의의 일부로 정의하십시오.

  • init 키워드(init?) 뒤에 물음표를 표시하여 실패할 수 있는 초기화 프로그램을 작성합니다.

    동일한 매개변수 유형 및 이름으로 실패 가능 및 실패 불가능 초기화를 정의할 수 없습니다.

  • 실패 가능한 이니셜라이저는 초기화하는 유형의 선택적 값을 만듭니다.

  • 초기화 실패가 트리거될 수 있는 지점을 나타내기 위해 실패 가능한 이니셜라이저 내에 return nil을 작성합니다.

    엄밀히 말하면 이니셜라이저는 값을 반환하지 않습니다.
    오히려, 그들의 역할은 초기화가 끝날 때까지 self가 완전하고 올바르게 초기화되도록 하는 것입니다.
    초기화 실패를 트리거하기 위해 return nil을 작성했지만 초기화 성공을 나타내기 위해 return 키워드를 사용하지 않습니다.

  • 예를 들어, 숫자 유형 변환을 위해 실패 가능한 이니셜라이저가 구현됩니다.

  • 숫자 유형 간의 변환이 값을 정확하게 유지하도록 하려면 init(exactly:) 이니셜라이저를 사용하십시오.

  • 유형 변환이 값을 유지할 수 없으면 초기화가 실패합니다.

    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 doesn't maintain value")
    }
    // Prints "3.14159 conversion to Int doesn't maintain value"
  • 아래 예제에서는 종이라는 상수 String 속성을 사용하여 Animal이라는 구조를 정의합니다.

  • Animal 구조는 또한 종이라는 단일 매개변수를 사용하여 실패할 수 있는 이니셜라이저를 정의합니다.

  • 이 이니셜라이저는 이니셜라이저에 전달된 종 값이 빈 문자열인지 확인합니다.

  • 빈 문자열이 발견되면 초기화 실패가 트리거됩니다. 그렇지 않으면 종 속성 값이 설정되고 초기화가 성공합니다.

    struct Animal {
        let species: String
        init?(species: String) {
            if species.isEmpty { return nil }
            self.species = species
        }
    }
  • 이 실패할 수 있는 이니셜라이저를 사용하여 새 Animal 인스턴스를 초기화하고 초기화가 성공했는지 확인할 수 있습니다.

    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 couldn't be initialized")
    }
    // Prints "The anonymous creature couldn't be initialized"

    빈 문자열 값(예: "Giraffe"가 아닌 "")을 확인하는 것은 선택적 String 값이 없음을 나타내기 위해 nil을 확인하는 것과 동일하지 않습니다.
    위의 예에서 빈 문자열("")은 선택 사항이 아닌 유효한 문자열입니다.
    그러나 동물이 종의 속성 값으로 빈 문자열을 갖는 것은 적절하지 않습니다.
    이런 제한을 모델링하기 위해 실패 가능한 이니셜라이저는 빈 문자열이 발견되면 초기화 실패를 트리거합니다.

📌 Failable Initializers for Enumerations

  • 실패 가능한 이니셜라이저를 사용하여 하나 이상의 매개변수를 기반으로 적절한 열거 케이스를 선택할 수 있습니다.

  • 그런 다음 제공된 매개변수가 적절한 열거 케이스와 일치하지 않으면 초기화가 실패할 수 있습니다.

  • 아래 예에서는 세 가지 가능한 상태(켈빈, 섭씨 및 화씨)가 있는 TemperatureUnit이라는 열거를 정의합니다.

  • 실패 가능한 이니셜라이저는 온도 기호를 나타내는 Character 값에 대한 적절한 열거 케이스를 찾는 데 사용됩니다.

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

Failable Initializers for Enumerations with Raw Values

  • 원시 값이 있는 열거형은 자동으로 실패할 수 있는 초기화 프로그램 init?(rawValue:)를 수신합니다.

  • 이 초기화 프로그램은 적절한 원시 값 유형의 rawValue라는 매개변수를 사용하고 발견된 경우 일치하는 열거형 케이스를 선택합니다 또는 일치하는 값이 없으면 초기화 실패를 트리거합니다.

  • 위의 TemperatureUnit 예제를 다시 작성하여 Character 유형의 원시 값을 사용하고 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."

📍 Propagation of Initialization Failure

  • 클래스, 구조 또는 열거형의 실패 가능한 이니셜라이저는 동일한 클래스, 구조 또는 열거형의 다른 실패 가능한 이니셜라이저에 위임할 수 있습니다.

  • 유사하게, 하위 클래스 실패 가능 이니셜라이저는 상위 클래스 실패 가능 이니셜라이저까지 위임할 수 있습니다.

  • 두 경우 모두 초기화를 실패하게 하는 다른 이니셜라이저에 위임하면 전체 초기화 프로세스가 즉시 실패하고 더 이상의 초기화 코드가 실행되지 않습니다.

    실패할 수 있는 이니셜라이저는 실패할 수 없는 이니셜라이저에 위임할 수도 있습니다. 다른 방법으로는 실패하지 않는 기존 초기화 프로세스에 잠재적인 실패 상태를 추가해야 하는 경우 이 접근 방식을 사용합니다.

  • 아래 예제는 CartItem이라는 Product의 하위 클래스를 정의합니다.

  • CartItem 클래스는 온라인 쇼핑 카트의 항목을 모델링합니다.

  • CartItem은 수량이라는 저장된 상수 속성을 도입하고 이 속성이 항상 최소 1의 값을 갖도록 합니다.

    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)
        }
  • CartItem의 실패 가능한 이니셜라이저는 1 이상의 수량 값을 받았는지 확인하는 것으로 시작합니다.

  • 수량이 유효하지 않으면 전체 초기화 프로세스가 즉시 실패하고 더 이상의 초기화 코드가 실행되지 않습니다.

  • 마찬가지로 Product의 failable initializer는 이름 값을 확인하고 name이 빈 문자열이면 초기화 프로세스가 즉시 실패합니다.

  • 비어 있지 않은 이름과 수량이 1 이상인 CartItem 인스턴스를 생성하면 초기화가 성공합니다.

    if let twoSocks = CartItem(name: "sock", quantity: 2) {
        print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
    }
    // Prints "Item: sock, quantity: 2"
  • 수량 값이 0인 CartItem 인스턴스를 생성하려고 하면 CartItem 초기화 프로그램으로 인해 초기화가 실패합니다.

    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"
  • 마찬가지로 이름 값이 비어 있는 CartItem 인스턴스를 만들려고 하면 슈퍼클래스 Product 이니셜라이저로 인해 초기화가 실패합니다.

    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"

📍 Overriding a Failable Initializer

  • 다른 이니셜라이저와 마찬가지로 서브클래스에서 슈퍼클래스 실패 가능 이니셜라이저를 재정의할 수 있습니다.
  • 또는 슈퍼클래스의 실패할 수 없는 이니셜라이저를 하위 클래스의 실패할 수 없는 이니셜라이저로 재정의할 수 있습니다.
  • 이를 통해 슈퍼클래스의 초기화가 실패하더라도 초기화가 실패할 수 없는 서브클래스를 정의할 수 있습니다.
  • 실패할 수 있는 슈퍼클래스 이니셜라이저를 실패할 수 없는 서브클래스 이니셜라이저로 재정의하는 경우 슈퍼클래스 이니셜라이저까지 위임하는 유일한 방법은 실패할 수 있는 슈퍼클래스 이니셜라이저의 결과를 강제로 해제하는 것입니다.

    실패할 수 없는 이니셜라이저를 실패할 수 없는 이니셜라이저로 재정의할 수 있지만 그 반대는 불가능합니다.

  • 아래 예제는 Document라는 클래스를 정의합니다. 이 클래스는 비어 있지 않은 문자열 값 또는 nil이지만 빈 문자열이 될 수 없는 name 속성으로 초기화할 수 있는 문서를 모델링합니다.
    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
        }
    }
  • 다음 예제에서는 AutomaticNamedDocument라는 Document의 하위 클래스를 정의합니다.
  • AutomaticNamedDocument 하위 클래스는 Document에 의해 도입된 지정된 초기화 프로그램 모두를 재정의합니다.
  • 이러한 재정의는 인스턴스가 이름 없이 초기화되거나 빈 문자열이 init(name:) 이니셜라이저에 전달되는 경우 AutomaticNamedDocument 인스턴스가 "[Untitled]"의 초기 이름 값을 갖도록 합니다.
    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
            }
        }
    }
  • AutomaticNamedDocument는 상위 클래스의 실패할 수 있는 init?(name:) 이니셜라이저를 실패할 수 없는 init(name:) 이니셜라이저로 재정의합니다.
  • AutomaticNamedDocument는 상위 클래스와 다른 방식으로 빈 문자열의 경우를 처리하기 때문에 초기화 프로그램이 실패할 필요가 없으므로 실패할 수 없는 버전의 초기화 프로그램을 대신 제공합니다.
  • 이니셜라이저에서 강제 해제를 사용하여 하위 클래스의 실패할 수 없는 이니셜라이저 구현의 일부로 슈퍼클래스에서 실패할 수 있는 이니셜라이저를 호출할 수 있습니다.
  • 예를 들어, 아래 UntitledDocument 하위 클래스의 이름은 항상 "[Untitled]"이며 초기화 중에 상위 클래스에서 실패할 수 있는 init(name:) 초기화 프로그램을 사용합니다.
    class UntitledDocument: Document {
        override init() {
            super.init(name: "[Untitled]")!
        }
    }
  • 이 경우 슈퍼클래스의 init(name:) 이니셜라이저가 이름으로 빈 문자열을 사용하여 호출된 경우 강제로 래핑 해제 작업을 수행하면 런타임 오류가 발생합니다.
  • 그러나 문자열 상수로 호출되기 때문에 초기화가 실패하지 않으므로 이 경우 런타임 오류가 발생하지 않음을 알 수 있습니다.

📍 The init! Failable Initializer

  • 일반적으로 init 키워드(init?) 뒤에 물음표를 배치하여 적절한 유형의 선택적 인스턴스를 생성하는 실패 가능한 이니셜라이저를 정의합니다.
  • 또는 적절한 유형의 암시적으로 래핑되지 않은 선택적 인스턴스를 생성하는 실패 가능한 이니셜라이저를 정의할 수 있습니다. 물음표 대신 init 키워드(init!) 뒤에 느낌표를 배치하여 이 작업을 수행합니다.
  • init? to init! 으로 위임할 수 있습니다. 그 반대의 경우도 마찬가지입니다.
  • init에서 init!로 위임할 수도 있지만 그렇게 하면 init! 초기화로 인해 초기화가 실패합니다.

📌 Required Initializers

  • 클래스 이니셜라이저의 정의 전에 필수 수정자를 작성하여 클래스의 모든 하위 클래스가 해당 이니셜라이저를 구현해야 함을 나타냅니다.
    class SomeClass {
        required init() {
            // initializer implementation goes here
        }
    }
  • 또한 필요한 이니셜라이저의 모든 하위 클래스 구현 전에 필수 수정자를 작성하여 이니셜라이저 요구 사항이 체인의 추가 하위 클래스에 적용됨을 나타내야 합니다.
  • 필수 지정 이니셜라이저를 재정의할 때 재정의 수정자를 작성하지 않습니다.
    class SomeSubclass: SomeClass {
        required init() {
            // subclass implementation of the required initializer goes here
        }
    }

    상속된 이니셜라이저로 요구 사항을 충족할 수 있다면 필수 이니셜라이저의 명시적 구현을 제공할 필요가 없습니다.

📌 Setting a Default Property Value with a Closure or Function

  • 저장된 속성의 기본값에 일부 사용자 지정 또는 설정이 필요한 경우 클로저 또는 전역 함수를 사용하여 해당 속성에 대한 사용자 지정 기본값을 제공할 수 있습니다.
  • 속성이 속한 유형의 새 인스턴스가 초기화될 때마다 클로저 또는 함수가 호출되고 반환 값이 속성의 기본값으로 할당됩니다.
  • 이러한 종류의 클로저 또는 함수는 일반적으로 속성과 동일한 유형의 임시 값을 만들고 원하는 초기 상태를 나타내도록 해당 값을 조정한 다음 속성의 기본값으로 사용할 임시 값을 반환합니다.
  • 다음은 클로저를 사용하여 기본 속성 값을 제공하는 방법에 대한 기본 개요입니다.
    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
        }()
    }
  • 클로저의 끝 중괄호 뒤에는 한 쌍의 빈 괄호가 옵니다. 이것은 스위프트에게 클로저를 즉시 실행하도록 지시합니다.
  • 이 괄호를 생략하면 클로저의 반환 값이 아니라 속성에 클로저 자체를 할당하려고 하는 것입니다.

    클로저를 사용하여 속성을 초기화하는 경우 클로저가 실행되는 시점에서 인스턴스의 나머지 부분이 아직 초기화되지 않았음을 기억하십시오. 즉, 해당 속성에 기본값이 있더라도 클로저 내에서 다른 속성 값에 액세스할 수 없습니다. 또한 암시적 self 속성을 사용하거나 인스턴스의 메서드를 호출할 수 없습니다.

  • 아래 예는 체스 게임용 보드를 모델링하는 Chessboard라는 구조를 정의합니다. 체스는 검은색과 흰색 사각형이 번갈아 가며 8 x 8 보드에서 진행됩니다.
  • 이 게임 보드를 나타내기 위해 Chessboard 구조에는 64개의 Bool 값 배열인 boardColors라는 단일 속성이 있습니다.
  • 배열에서 true 값은 검은색 사각형을 나타내고 false 값은 흰색 사각형을 나타냅니다.
  • 배열의 첫 번째 항목은 보드의 왼쪽 상단 사각형을 나타내고 배열의 마지막 항목은 보드의 오른쪽 하단 사각형을 나타냅니다.
  • boardColors 배열은 색상 값을 설정하기 위해 클로저로 초기화됩니다.
    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]
        }
    }
  • 새로운 Chessboard 인스턴스가 생성될 때마다 클로저가 실행되고 boardColors의 기본값이 계산되어 반환됩니다.
  • 위 예제의 클로저는 임시 보드라는 임시 배열에서 보드의 각 사각형에 적절한 색상을 계산하고 설정하고 설정이 완료되면 이 임시 배열을 클로저의 반환 값으로 반환합니다.
  • 반환된 배열 값은 boardColors에 저장되며 squareIsBlackAt(row:column:) 유틸리티 함수로 쿼리할 수 있습니다.
    let board = Chessboard()
    print(board.squareIsBlackAt(row: 0, column: 1))
    // Prints "true"
    print(board.squareIsBlackAt(row: 7, column: 7))
    // Prints "false"
profile
I Am Groot

0개의 댓글