초기화는 사용 할 클래스, 구조체 또는 열거형의 인스턴스를 생성하기전 준비하는 프로세스입니다. 이 프로세스에는 해당 인스턴스에 저장된 각 프로퍼티의 초기 값을 설정하고 새 인스턴스를 사용할 준비가 되기 전에 필요한 다른 설정 또는 초기화를 수행하는 작업이 포함됩니다.
특정 유형의 새 인스턴스를 만들기 위해 호출 할 수 있는 특수 메서드와 같은 이니셜 라이저를 정의하여 이 초기화 프로세스를 구현합니다. Objective-C 이니셜 라이저와 달리 Swift 이니셜 라이저는 값을 반환하지 않습니다. 기본 역할은 해당 타입의 새 인스턴스가 처음 사용되기 전에 올바르게 초기화되었는지 확인하는 것입니다.
클래스 타입의 인스턴스는 해당 클래스의 인스턴스가 메모리 해제되기 직전에 사용자 지정 클린업을 수행하는 초기화 deinitializer를 구현할 수도 있습니다. deinitializer에 대한 자세한 내용은 [Deinitialization]을 참조하십시오.
Setting Initial Values for Stored Properties
저장 프로퍼티에 대한 초기 값 설정
클래스 및 구조체는 해당 클래스 또는 구조체의 인스턴스가 생성 될 때까지 모든 프로퍼티를 적절한 초기 값으로 설정해야합니다. 저장 프로퍼티는 불확실한 상태로 남을 수 없습니다.
이니셜 라이저 내에서 프로퍼티에 대한 초기 값을 설정하거나 프로퍼티 정의의 일부로 기본 프로퍼티의 값을 할당 할 수 있습니다. 이러한 작업은 다음 섹션에서 설명합니다.
NOTE
저장 프로퍼티에 기본값을 할당하거나 이니셜 라이저 내에서 초기 값을 설정하면 속성 관찰자(property observers)를 호출하지 않고 해당 프로퍼티의 값이 직접 설정됩니다.
Initializers
이니셜라이저
이니셜 라이저는 특정 타입의 새 인스턴스를 만들기 위해 호출됩니다. 가장 간단한 형태의 초기화는 매개 변수가 없는 인스턴스 메소드와 같으며 init 키워드를 사용하여 작성됩니다.
init() {
// 여기서 초기화를 수행하세요.
}
아래 예는 화씨 눈금으로 표현 된 온도를 저장하기 위해 화씨라는 새로운 구조체를 정의합니다. Fahrenheit 구조에는 Double 타입의 하나의 저장 프로퍼티인 temperature가 있습니다.
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("기본 온도는 화씨 \(f.temperature)° 입니다.")
// Prints "기본 온도는 화씨 32.0° 입니다."
이 구조체는 매개 변수가 없는 단일 이니셜 라이저인 init를 정의하여 저장 프로퍼티에 온도를 32.0 (화씨 단위의 물의 어는점) 값으로 초기화 합니다.
Default Property Values
기본 속성 값
위와 같이 초기화 프로그램 내에서 프로퍼티의 초기 값을 설정할 수 있습니다. 또는 프로퍼티 선언의 일부로 기본 프로퍼티 값을 지정합니다. 프로퍼티가 정의 될 때 프로퍼티에 초기 값을 할당하여 기본 프로퍼티 값을 지정합니다.
NOTE
프로퍼티가 항상 동일한 초기 값을 사용하는 경우 이니셜 라이저 내에서 값을 설정하는 대신 기본값을 제공하세요. 최종 결과는 동일하지만 기본값은 프로퍼티의 초기화를 선언에 더 가깝게 연결합니다. 더 짧고 명확한 이니셜 라이저를 만들고 기본값에서 프로퍼티 타입을 추론 할 수 있습니다. 또한 기본값을 사용하면이 장의 뒷부분에서 설명하는 것처럼 기본 이니셜 라이저 및 이니셜 라이저 상속을 더 쉽게 활용할 수 있습니다.
프로퍼티가 선언 된 지점에서 temperature 프로퍼티에 대한 기본값을 제공하여 위의 Fahrenheit 구조를 더 간단한 형식으로 작성할 수 있습니다.
struct Fahrenheit {
var temperature = 32.0
}
Customizing Initialization
사용자 초기화
다음 섹션에 설명 된대로 입력 매개 변수 및 옵셔널 타입을 사용하거나 초기화 중에 상수 프로퍼티를 할당하여 초기화 프로세스를 사용자가 지정 할 수 있습니다.
Initialization Parameters
초기화 매개 변수
초기화 프로세스를 사용자가 정의하는 값의 타입과 이름을 정의하기 위해 초기화 매개 변수를 초기화 정의의 일부로 제공 할 수 있습니다. 초기화 매개 변수는 기능 및 메소드 매개 변수와 동일한 기능 및 구문을 갖습니다.
다음 예제는 섭씨로 표현 된 온도를 저장하는 섭씨라는 구조체를 정의합니다. Celsius 구조는 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이고 매개 변수 이름이 fahrenheit 인 단일 초기화 매개 변수가 있습니다. 두 번째 이니셜 라이저에는 인수 레이블이 fromKelvin이고 매개 변수 이름이 kelvin 인 단일 초기화 매개 변수가 있습니다. 두 초기화 프로그램 모두 단일 인수를 해당 섭씨 값으로 변환하고이 값을 temperatureInCelsius라는 프로퍼티에 저장합니다.
Parameter Names and Argument Labels
매개 변수 이름 및 인수 레이블
함수 및 메서드 매개 변수와 마찬가지로 초기화 매개 변수에는 이니셜 라이저 본문 내에서 사용할 매개 변수 이름과 이니셜 라이저를 호출 할 때 사용할 인수 레이블이 모두 있을 수 있습니다.
그러나 이니셜 라이저는 함수 및 메서드와 같이 괄호 앞에 식별 함수 이름이 없습니다. 따라서 이니셜 라이저 매개 변수의 이름과 타입은 어떤 이니셜 라이저를 호출해야하는지 식별하는 데 특히 중요한 역할을합니다. 이 때문에 Swift는 초기화 프로그램의 모든 매개 변수에 대해 자동 인수 레이블을 제공합니다.
다음 예제에서는 빨강, 녹색 및 파랑이라는 세 가지 상수 프로퍼티를 사용하여 Color라는 구조체를 정의합니다. 이러한 프로퍼티는 0.0에서 1.0 사이의 값을 저장하여 색상의 빨강, 녹색 및 파랑 값을 나타냅니다.
Color는 빨강, 녹색 및 파랑 구성 요소에 대해 Double 타입의 적절하게 명명 된 세 개의 매개 변수를 가진 이니셜 라이저를 제공합니다. Color는 또한 세 가지 색상 구성 요소 모두에 대해 동일한 값을 제공하는 데 사용되는 단일 white 매개 변수가 있는 두 번째 이니셜 라이저를 제공합니다.
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)
// 컴파일 오류가 납니다. 인수 레이블이 필요
인수 레이블이 없는 이니셜 라이저 매개 변수
Initializer Parameters Without Argument Labels
이니셜 라이저 매개 변수에 인수 레이블을 사용하지 않으려면 해당 매개 변수에 대한 명시 적 인수 레이블 대신 밑줄 (_)을 작성하여 기본 동작을 재정의합니다.
다음은 위의 초기화 매개 변수에 있는 Celsius 예제의 확장 된 버전입니다. 이니셜 라이저가 추가되어 이미 섭씨 눈금에있는 Double 값에서 새 Celsius 인스턴스를 만들 수 있습니다.
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 값으로 자동 초기화됩니다. 이는 초기화 중에 프로퍼티가 의도적으로 “아직 값이 없음”을 갖도록 의도되었음을 나타냅니다.
다음 예제는 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: "치즈를 좋아하세요?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
설문 조사 질문에 대한 응답은 질문을 받을 때까지 알 수 없으므로 response 프로퍼티는 String? 또는 “optional String”타입으로 선언됩니다. SurveyQuestion의 새 인스턴스가 초기화 될 때 “아직 문자열이 없음”을 의미하는 기본값 nil이 자동으로 할당됩니다.
Assigning Constant Properties During Initialization
초기화 중 상수 프로퍼티 할당
초기화 중 언제든지 상수 프로퍼티에 값을 할당 할 수 있습니다.
초기화가 완료 될 때까지 명확한 값으로 설정됩니다. 상수 프로퍼티에 값이 할당되면 더 이상 수정할 수 없습니다.
NOTE
클래스 인스턴스의 경우 상수 프로퍼티는 이를 도입하는 클래스에 의해서만 초기화 중에 수정 될 수 있습니다. 하위 클래스에서 수정할 수 없습니다.
위의 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: "사탕무는 어때?")
beetsQuestion.ask()
// Prints "사탕무는 어때?"
beetsQuestion.response = "나는 사탕무도 좋아합니다. (그러나 치즈는 좋아하지 않습니다.)"
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
구조체 타입의 맴버와이즈 이니셜 라이저
구조체 타입은 자체 사용자 정의 이니셜 라이저를 정의하지 않으면 자동으로 멤버별 이니셜 라이저가 적용됩니다. 기본 이니셜 라이저와 달리 구조체는 기본값이없는 프로퍼티가 저장되어 있어도 멤버 별 이니셜 라이저를 받습니다.
멤버 단위 이니셜 라이저는 새 구조체 인스턴스의 멤버 프로퍼티를 초기화하는 간단한 방법입니다. 새 인스턴스의 프로퍼티에 대한 초기 값은 이름으로 멤버 단위 이니셜 라이저에 전달할 수 있습니다.
아래 예제는 width 및 height라는 두 가지 프로퍼티를 사용하여 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를 호출 할 수 있습니다.
값 타입에 대한 사용자 정의 이니셜 라이저를 정의하면 해당 유형의 기본 이니셜 라이저 (또는 구조체인 경우 멤버 단위 이니셜 라이저)에 더 이상 액세스 할 수 없습니다. 이 제약은 자동 이니셜 라이저 중 하나를 사용하는 누군가가 더 복잡한 이니셜 라이저에서 제공하는 추가 필수 설정을 실수로 우회하는 상황을 방지합니다.
NOTE
기본 이니셜 라이저, 멤버 별 이니셜 라이저 및 자체 사용자 정의 이니셜 라이저를 사용하여 사용자 정의 값 타입을 초기화 하려면 값 타입의 원래 구현의 일부가 아닌 확장에 사용자 정의 이니셜 라이저를 작성여야 합니다. 자세한 내용은 확장(Extensions.)을 참조하십시오.
다음 예제에서는 기하학적 직사각형을 나타내는 사용자 지정 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으로 초기화 된 원점 및 크기 프로퍼티 값을 사용하거나 특정 원점 및 크기를 제공 또는 특정 중심점 및 크기를 제공하는 세 가지 방법 중 하나로 초기화 할 수 있습니다. 이러한 초기화 옵션은 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의 중심값 은 (0.0, 0.0) 이고 사이즈는 (0.0, 0.0) 입니다.
두 번째 Rect 이니셜 라이저 인 init (origin : size : )는 자체 사용자 정의 이니셜 라이저가 없는 경우 구조체가 수신 했을 멤버 단위 이니셜 라이저와 기능적으로 동일합니다. 이 이니셜 라이저는 단순히 원본 및 크기 인수 값을 적절한 프로퍼티에 할당합니다.
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect의 중심값은 (2.0, 2.0) 이고 크기는 (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의 중심값 은 (2.5, 2.5) 이고 크기는 (3.0, 3.0) 입니다.
init (center : size : ) 이니셜 라이저는 중심정과 크기의 새 값을 적절한 프로퍼티 자체에 할당 할 수 있습니다. 그러나 init (center : size : ) 이니셜 라이저가 해당 기능을 이미 제공하는 기존 이니셜 라이저를 활용하는 것이 더 편리하고 명확합니다.
NOTE
init () 및 init (origin : size : ) 이니셜 라이저를 직접 정의하지 않고 이 예제를 작성하는 다른 방법은 확장[Extensions.]을 참조하십시오.
Class Inheritance and Initialization
클래스 상속 및 초기화
클래스가 수퍼 클래스에서 상속하는 모든 프로퍼티를 포함하여 클래스의 모든 프로퍼티에는 초기화 중에 초기 값이 할당되어야합니다.
Swift는 모든 프로퍼티의 초기 값을 받을 수 있도록 클래스 타입에 대해 두 가지 종류의 이니셜 라이저를 정의합니다. 이들은 지정된 이니셜 라이저 및 편의(convenience) 이니셜 라이저로 알려져 있습니다.
Designated Initializers and Convenience Initializers
지정된 이니셜 라이저 및 편의 이니셜 라이저
지정된 이니셜 라이저는 클래스의 기본 이니셜 라이저입니다. 지정된 이니셜 라이저는 해당 클래스가 도입 한 모든 프로퍼티를 완전히 초기화하고 적절한 수퍼 클래스 이니셜 라이저를 호출하여 수퍼 클래스 체인까지 초기화 프로세스를 계속합니다.
클래스에는 지정된 이니셜 라이저가 거의 없는 경향이 있으며 클래스에는 하나만있는 것이 일반적입니다. 지정된 이니셜 라이저는 초기화가 수행되고 초기화 프로세스가 수퍼 클래스 체인까지 계속되는 “funnel깔때기“지점입니다.
모든 클래스에는 지정된 이니셜 라이저가 하나 이상 있어야합니다. 경우에 따라이 요구 사항은 아래의 자동 이니셜 라이저 상속에 설명된대로 수퍼 클래스에서 하나 이상의 지정된 이니셜 라이저를 상속하여 충족됩니다.
편의 이니셜 라이저는 보조 이니셜 라이저로 클래스에 대한 이니셜 라이저를 지원합니다. 지정된 이니셜 라이저의 일부 매개 변수를 기본값으로 설정하여 편의 이니셜 라이저와 동일한 클래스에서 지정된 이니셜 라이저를 호출하도록 편의 이니셜 라이저를 정의 할 수 있습니다. 또한 편의 이니셜 라이저를 정의하여 특정 사용 사례 또는 입력 값 유형에 대한 해당 클래스의 인스턴스를 만들 수 있습니다.
클래스에 필요하지 않은 경우 편의 이니셜 라이저를 제공 할 필요가 없습니다. 일반적인 초기화 패턴에 대한 바로 가기가 시간을 절약하거나 의도에서 클래스 초기화를 더 명확하게 만들 때마다 편리한 이니셜 라이저를 만듭니다.
Syntax for Designated and Convenience Initializers
지정 및 편의 이니셜 라이저 구문
클래스에 지정된 이니셜 라이저는 값 타입에 대한 간단한 이니셜 라이저와 동일한 방식으로 작성됩니다.
init(parameters) {
내용
}
편의 이니셜 라이저는 동일한 스타일로 작성되지만 convenience 수정자가 init 키워드 앞에 공백으로 구분되어 배치됩니다.
convenience init(매개변수) {
statements
}
Initializer Delegation for Class Types
클래스 타입에 대한 초기화 위임
지정된 이니셜 라이저와 편의 이니셜 라이저 간의 관계를 단순화하기 위해 Swift는 이니셜 라이저 간의 위임 호출에 대해 다음 세 가지 규칙을 적용합니다.
규칙 1
지정된 이니셜 라이저는 직계 수퍼 클래스에서 지정된 이니셜 라이저를 호출해야합니다.
규칙 2
편의 이니셜 라이저는 동일한 클래스에서 다른 이니셜 라이저를 호출해야합니다.
규칙 3
편의 이니셜 라이저는 궁극적으로 지정된 이니셜 라이저를 호출해야합니다.
이것을 기억하는 간단한 방법은 다음과 같습니다.
- 지정된 이니셜 라이저는 항상 위임해야합니다.
- 편의 이니셜 라이저는 항상 위임해야합니다.
이러한 규칙은 아래 그림에 설명되어 있습니다.
여기에서 수퍼 클래스에는 지정된 이니셜 라이저 하나와 편의 이니셜 라이저 두 개가 있습니다. 하나의 편의 이니셜 라이저는 다른 편의 이니셜 라이저를 호출하고, 이는 차례로 지정된 단일 이니셜 라이저를 호출합니다. 이는 위의 규칙 2 및 3을 충족합니다. 수퍼 클래스 자체에는 추가 수퍼 클래스가 없으므로 규칙 1이 적용되지 않습니다.
이 그림의 하위 클래스에는 두 개의 지정된 이니셜 라이저와 하나의 편의 이니셜 라이저가 있습니다. 편의 이니셜 라이저는 동일한 클래스에서 다른 이니셜 라이저 만 호출 할 수 있으므로 지정된 두 이니셜 라이저 중 하나를 호출해야합니다. 이는 위의 규칙 2 및 3을 충족합니다. 지정된 이니셜 라이저는 모두 위의 규칙 1을 충족하기 위해 수퍼 클래스에서 지정된 단일 이니셜 라이저를 호출해야합니다.
NOTE
이러한 규칙은 클래스 사용자가 각 크래스의 인스턴스를 만드는 방법에 영향을주지 않습니다. 위 다이어그램의 모든 이니셜 라이저를 사용하여 자신이 속한 클래스의 완전히 초기화 된 인스턴스를 만들 수 있습니다. 규칙은 클래스의 이니셜 라이저 구현을 작성하는 방법에만 영향을줍니다.
아래 그림은 4 개의 클래스에 대한 보다 복잡한 클래스 계층 구조를 보여줍니다. 이 계층 구조에서 지정된 이니셜 라이저가 클래스 초기화를 위한 “funnel”지점으로 작동하여 체인에있는 클래스 간의 상호 관계를 단순화하는 방법을 보여줍니다.
Two-Phase Initialization
2단계 초기화
Swift의 클래스 초기화는 2 단계 프로세스입니다. 첫 번째 단계에서는 저장된 각 프로퍼티에 해당 프로퍼티를 도입 한 클래스에 의해 초기 값이 할당됩니다. 모든 저장된 프로퍼티의 초기 상태가 결정되면 두 번째 단계가 시작되고 새 인스턴스를 사용할 준비가 된 것으로 간주되기 전에 각 클래스에 저장된 프로퍼티를 추가로 사용자 지정할 수 있는 기회가 제공됩니다.
2 단계 초기화 프로세스를 사용하면 초기화를 안전하게하는 동시에 클래스 계층 구조의 각 클래스에 완전한 유연성을 제공합니다. 2 단계 초기화는 프로퍼티 값이 초기화되기 전에 액세스되는 것을 방지하고 다른 초기화 프로그램이 예기치 않게 프로퍼티 값을 다른 값으로 설정하는 것을 방지합니다.
NOTE
Swift의 2 단계 초기화 프로세스는 Objective-C의 초기화와 유사합니다. 주요 차이점은 1 단계 동안 Objective-C는 모든 속성에 0 또는 null 값 (예 : 0 또는 nil)을 할당한다는 것입니다. Swift의 초기화 흐름은 사용자 정의 초기 값을 설정할 수 있고 0 또는 nil이 유효한 기본값이 아닌 유형에 대처할 수 있다는 점에서 더 유연합니다.
Swift의 컴파일러는 2 단계 초기화가 오류없이 완료되었는지 확인하기 위해 4 가지 유용한 안전 검사를 수행합니다.
안전 점검 1
지정된 이니셜 라이저는 수퍼 클래스 이니셜 라이저로 위임하기 전에 해당 클래스에 의해 도입 된 모든 프로퍼티가 초기화되었는지 확인합니다.
위에서 언급했듯이 객체의 메모리는 저장된 모든 프로퍼티의 초기 상태가 알려지면 완전히 초기화 된 것으로 간주됩니다. 이 규칙이 충족 되려면 지정된 이니셜 라이저가 체인을 넘기기 전에 모든 속성이 초기화되었는지 확인해야합니다.
안전 점검 2
지정된 이니셜 라이저는 상속 된 프로퍼티에 값을 할당하기 전에 수퍼 클래스 이니셜 라이저까지 위임해야합니다. 그렇지 않으면 지정된 이니셜 라이저가 할당 한 새 값이 자체 초기화의 일부로 수퍼 클래스에 의해 덮어 쓰여집니다.
안전 점검 3
편의 이니셜 라이저는 프로퍼티 (동일한 클래스에서 정의한 프로퍼티 포함)에 값을 할당하기 전에 다른 이니셜 라이저에 위임해야합니다. 그렇지 않은 경우 편의 이니셜 라이저가 할당하는 새 값을 자체 클래스의 지정된 이니셜 라이저로 덮어 씁니다.
안전 점검 4
초기화 프로그램은 초기화의 첫 번째 단계가 완료 될 때까지 인스턴스 메서드를 호출하거나 인스턴스 프로퍼티의 값을 읽거나 self를 값으로 참조 할 수 없습니다.
클래스 인스턴스는 첫 번째 단계가 끝날 때까지 완전히 유효하지 않습니다. 첫 번째 단계가 끝날 때 클래스 인스턴스가 유효한 것으로 확인 된 후에 만 프로퍼티에 액세스 할 수 있고 메서드를 호출 할 수 있습니다.
위의 4 가지 안전 검사를 기반으로 2 단계 초기화가 수행되는 방식은 다음과 같습니다.
1 단계
- 클래스에서 지정 또는 편의 초기화가 호출됩니다.
- 해당 클래스의 새 인스턴스에 대한 메모리가 할당됩니다. 메모리가 아직 초기화되지 않았습니다.
- 해당 클래스에 대해 지정된 이니셜 라이저는 해당 클래스가 도입 한 모든 프로퍼티에 값이 있는지 확인합니다.
- 이제 이러한 프로퍼티에 대한 메모리가 초기화됩니다.
- 지정된 이니셜 라이저는 수퍼 클래스 이니셜 라이저에게 전달되어 자체 프로퍼티에 대해 동일한 작업을 수행합니다.
- 이것은 체인의 맨 위에 도달 할 때까지 클래스 상속 체인 위로 계속됩니다.
- 체인의 맨 위에 도달하고 체인의 최종 클래스가 저장된 모든 프로퍼티에 값이 있는지 확인하면 인스턴스의 메모리가 완전히 초기화 된 것으로 간주되고 1 단계가 완료됩니다.
2 단계
- 체인의 맨 위에서 아래로 작업하면 체인에 지정된 각 이니셜 라이저는 인스턴스를 추가로 사용자 지정할 수있는 옵션이 있습니다. 이니셜 라이저는 이제 self에 액세스 할 수 있으며 프로퍼티를 수정하고 인스턴스 메서드를 호출하는 등의 작업을 수행 할 수 있습니다.
- 마지막으로, 체인의 모든 편의 이니셜 라이저는 인스턴스를 사용자 정의하고 self와 함께 작업 할 수있는 옵션이 있습니다.
다음은 1 단계에서 가상 하위 클래스 및 수퍼 클래스에 대한 초기화 호출을 찾는 방법입니다.
이 예에서 초기화는 하위 클래스의 편의 이니셜 라이저를 호출하는 것으로 시작됩니다. 이 편리한 이니셜 라이저는 아직 프로퍼티를 수정할 수 없습니다. 동일한 클래스에서 지정된 이니셜 라이저에 위임합니다.
지정된 이니셜 라이저는 안전 검사 1에 따라 모든 하위 클래스의 속성에 값이 있는지 확인합니다. 그런 다음 수퍼 클래스에서 지정된 이니셜 라이저를 호출하여 체인까지 초기화를 계속합니다.
수퍼 클래스의 지정된 이니셜 라이저는 모든 수퍼 클래스 프로퍼티에 값이 있는지 확인합니다. 초기화 할 추가 슈퍼 클래스가 없으므로 추가 위임이 필요하지 않습니다.
수퍼 클래스의 모든 속성이 초기 값을 가지 자마자 해당 메모리는 완전히 초기화 된 것으로 간주되고 1 단계가 완료됩니다.
2 단계에서 동일한 초기화 호출을 찾는 방법은 다음과 같습니다.
이제 수퍼 클래스의 지정된 이니셜 라이저가 인스턴스를 추가로 사용자 정의 할 수 있습니다 (그럴 필요는 없음).
수퍼 클래스의 지정된 이니셜 라이저가 완료되면 하위 클래스의 지정된 이니셜 라이저가 추가 사용자 정의를 수행 할 수 있습니다 (다시 말하지만 그럴 필요는 없음).
마지막으로 하위 클래스의 지정된 이니셜 라이저가 완료되면 원래 호출되었던 편의 이니셜 라이저가 추가 사용자 정의를 수행 할 수 있습니다.
Initializer Inheritance and Overriding
초기화 상속 및 재정의
Objective-C의 서브 클래스와 달리 Swift 서브 클래스는 기본적으로 수퍼 클래스 이니셜 라이저를 상속하지 않습니다. Swift의 접근 방식은 슈퍼 클래스의 간단한 이니셜 라이저가보다 전문화 된 하위 클래스에 상속되고 완전히 또는 올바르게 초기화되지 않은 하위 클래스의 새 인스턴스를 만드는 데 사용되는 상황을 방지합니다.
NOTE
슈퍼 클래스 이니셜 라이저는 특정 상황에서 상속되지만 그렇게하는 것이 안전하고 적절한 경우에만 상속됩니다. 자세한 내용은 아래의 자동 이니셜 라이저 상속을 참조하십시오.
사용자 정의 서브 클래스가 수퍼 클래스와 동일한 이니셜 라이저 중 하나 이상을 표시하도록하려면 서브 클래스 내에서 해당 이니셜 라이저의 사용자 정의 구현을 제공 할 수 있습니다.
슈퍼 클래스로 지정된 이니셜 라이저와 일치하는 하위 클래스 이니셜 라이저를 작성할 때 지정된 이니셜 라이저의 오버라이드를 효과적으로 제공하는 것입니다. 따라서 서브 클래스의 이니셜 라이저 정의 전에 override 수정자를 작성해야합니다. 기본 이니셜 라이저에 설명 된대로 자동으로 제공된 기본 이니셜 라이저를 재정의하는 경우에도 마찬가지입니다.
재정의 된 속성, 메서드 또는 아래 첨자와 마찬가지로 override 수정자가 있으면 Swift가 슈퍼 클래스에 재정의 할 일치하는 지정된 이니셜 라이저가 있는지 확인하고 재정의하는 이니셜 라이저의 매개 변수가 의도 한대로 지정되었는지 확인하라는 메시지가 표시됩니다.
NOTE
하위 클래스의 이니셜 라이저 구현이 편리한 이니셜 라이저 인 경우에도 수퍼 클래스로 지정된 이니셜 라이저를 재정의 할 때 항상 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 버전은 override 수정자로 표시됩니다.
Bicycle 용 init () 이니셜 라이저는 Bicycle 클래스의 수퍼 클래스 인 Vehicle에 대한 기본 이니셜 라이저를 호출하는 super.init () 호출로 시작됩니다. 이렇게하면 Bicycle이 프로퍼티를 수정할 수 있는 기회를 갖기 전에 numberOfWheels 상속 프로퍼티가 Vehicle에 의해 초기화됩니다. super.init ()를 호출 한 후 numberOfWheels의 원래 값이 새 값 2로 대체됩니다.
Bicycle 인스턴스를 만드는 경우 상속 된 description 계산 프로퍼티를 호출하여 numberOfWheels 프로퍼티가 어떻게 업데이트 되었는지 확인할 수 있습니다.
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
하위 클래스 이니셜 라이저가 초기화 프로세스의 2 단계에서 사용자 정의를 수행하지 않고 수퍼 클래스에 인수가 없는 지정된 이니셜 라이저가 있는 경우 모든 하위 클래스의 저장된 프로퍼티에 값을 할당 한 후 super.init () 호출을 생략 할 수 있습니다.
이 예제는 Hoverboard라는 Vehicle의 또 다른 하위 클래스를 정의합니다. 이니셜 라이저에서 Hoverboard 클래스는 색상 프로퍼티만 설정합니다. 이 이니셜 라이저는 super.init ()를 명시 적으로 호출하는 대신 수퍼 클래스의 이니셜 라이저에 대한 암시 적 호출을 사용하여 프로세스를 완료합니다.
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() 암시적으로 호출
}
override var description: String {
return "아름다운 \(color)의 \(super.description)
}
}
Hoverboard의 인스턴스는 Vehicle 이니셜 라이저에서 제공하는 기본 휠 수를 사용합니다.
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// 호버 보드 : 아름다운 은색의 바퀴 0 개
NOTE
하위 클래스는 초기화 중에 상속 된 변수 프로퍼티를 수정할 수 있지만 상속 된 상수 프로퍼티는 수정할 수 없습니다.
Automatic Initializer Inheritance
자동 이니셜 라이저 상속
위에서 언급했듯이 하위 클래스는 기본적으로 수퍼 클래스 이니셜 라이저를 상속하지 않습니다. 그러나 특정 조건이 충족되면 슈퍼 클래스 이니셜 라이저가 자동으로 상속됩니다. 실제로 이것은 많은 일반적인 시나리오에서 이니셜 라이저 재정의를 작성할 필요가 없으며 안전 할 때마다 최소한의 노력으로 슈퍼 클래스 이니셜 라이저를 상속 할 수 있음을 의미합니다.
하위 클래스에 도입 한 새 프로퍼티에 대한 기본값을 제공한다고 가정하면 다음 두 가지 규칙이 적용됩니다.
규칙 1
하위 클래스가 지정된 이니셜 라이저를 정의하지 않으면 수퍼 클래스로 지정된 이니셜 라이저를 모두 자동으로 상속합니다.
규칙 2
하위 클래스가 규칙 1에 따라 상속하거나 정의의 일부로 사용자 지정 구현을 제공하여 모든 수퍼 클래스 지정 이니셜 라이저의 구현을 제공하면 모든 수퍼 클래스 편의 이니셜 라이저를 자동으로 상속합니다.
이 규칙은 하위 클래스가 편의 이니셜 라이저를 추가하는 경우에도 적용됩니다.
NOTE
서브 클래스는 규칙 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 클래스는 인수없이 convenience 이니셜 라이저 init ()도 제공합니다. init () 이니셜 라이저는 이름 값이 [Unnamed] 인 Food 클래스의 init (name : String)에 위임하여 새 음식에 대한 기본 자리 표시 자 이름을 제공합니다.
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
계층 구조의 두 번째 클래스는 RecipeIngredient라는 Food의 하위 클래스입니다. RecipeIngredient 클래스는 요리 레시피의 재료를 모델링합니다. quantity 이라는 Int 프로퍼티 (Food에서 상속하는 이름 속성에 추가로)를 도입하고 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)라는 단일 지정된 이니셜 라이저가 있습니다. 이 이니셜 라이저는 전달 된 quantity 인수를 quantity 프로퍼티에 할당하여 시작합니다.이 프로퍼티는RecipeIngredient에 의해 도입 된 유일한 새 프로퍼티 입니다. 이렇게 하면 초기화 프로그램은 Food 클래스의 init (name : String) 초기화 프로그램까지 위임합니다. 이 과정은 위의 Two-Phase Initialization의 안전 점검 1을 만족합니다.
RecipeIngredient는 또한 이름만으로 RecipeIngredient 인스턴스를 생성하는 데 사용되는 convenience 이니셜 라이저 init (name : String)을 정의합니다. 이 convenience 이니셜 라이저는 명시 적 수량없이 생성 된 RecipeIngredient 인스턴스에 대해 수량 1을 가정합니다. 이 convenience 이니셜 라이저를 정의하면 RecipeIngredient 인스턴스를 더 빠르고 편리하게 만들 수 있으며 여러 단일 수량 RecipeIngredient 인스턴스를 만들 때 코드 중복을 방지 할 수 있습니다. 이 편의 이니셜 라이저는 단순히 수량 값 1을 전달하여 클래스의 지정된 이니셜 라이저에 위임합니다.
RecipeIngredient에서 제공하는 init (name : String) convenience 이니셜 라이저는 Food에서 지정한 init (name : String) 이니셜 라이저와 동일한 매개 변수를 사용합니다. 이 편리한 이니셜 라이저는 수퍼 클래스에서 지정된 이니셜 라이저를 재정의하기 때문에 override 한정자로 표시되어야합니다 (초기화 상속 및 재정의에 설명 됨).
RecipeIngredient는 convenience 이니셜 라이저로 init (name : String) 이니셜 라이저를 제공하지만 그럼에도 불구하고 RecipeIngredient는 수퍼 클래스의 지정된 이니셜 라이저의 구현을 모두 제공했습니다. 따라서 RecipeIngredient는 수퍼 클래스의 모든 convenience 이니셜 라이저도 자동으로 상속합니다.
이 예제에서 RecipeIngredient의 수퍼 클래스는 Food이며 init ()라는 단일 convenience 이니셜 라이저가 있습니다. 따라서 이 이니셜 라이저는 RecipeIngredient에 상속됩니다. 상속 된 init () 버전은 Food 버전이 아닌 RecipeIngredient 버전의 init (name : String)에 위임한다는 점을 제외하고는 Food 버전과 동일한 방식으로 작동합니다.
이 세 가지 이니셜 라이저는 모두 새로운 RecipeIngredient 인스턴스를 만드는 데 사용할 수 있습니다.
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "베이컨")
let sixEggs = RecipeIngredient(name: "달걀", quantity: 6)
계층 구조의 세 번째이자 마지막 클래스는 ShoppingListItem이라는 RecipeIngredient의 하위 클래스입니다. ShoppingListItem 클래스는 쇼핑 목록에 표시되는 레시피 성분을 모델링합니다.
쇼핑 목록의 모든 항목은 “미 구매”로 시작합니다. 이 사실을 나타 내기 위해 ShoppingListItem은 기본값이 false 인 Buyed라는 부울 프로퍼티를 도입합니다. ShoppingListItem은 또한 ShoppingListItem 인스턴스의 텍스트 설명을 제공하는 계산 된 설명 프로퍼티를 추가합니다.
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
NOTE
ShoppingListItem은 여기에서 모델링 된 쇼핑 목록의 항목이 항상 구매되지 않은 상태로 시작하기 때문에 구매 한 항목의 초기 값을 제공하는 이니셜 라이저를 정의하지 않습니다.
모든 프로퍼티에 대한 기본값을 제공하고 이니셜 라이저 자체를 정의하지 않기 때문에 ShoppingListItem은 수퍼 클래스에서 지정된 모든 convenience 이니셜 라이저를 자동으로 상속합니다.
아래 그림은 세 클래스 모두에 대한 전체 이니셜 라이저 체인을 보여줍니다.
상속 된 세 가지 이니셜 라이저를 모두 사용하여 새 ShoppingListItem 인스턴스를 만들 수 있습니다.
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "베이컨"),
ShoppingListItem(name: "달걀", quantity: 6),
]
breakfastList[0].name = "오렌지 쥬스"
breakfastList[0].purchased = true
breakfastList.forEach { print(item.description) }
// 1 x 오렌지 쥬스 ✔
// 1 x 베이컨 ✘
// 6 x 달걀 ✘
여기에서 breakfastList라는 새 배열이 3 개의 새 ShoppingListItem 인스턴스를 포함하는 배열 리터럴에서 생성됩니다. 배열 유형은 [ShoppingListItem]으로 유추됩니다. 배열이 생성되면 배열 시작 부분의 ShoppingListItem 이름이 “[Unnamed]”에서 “Orange juice”로 변경되고 구매 된 것으로 표시됩니다. 배열의 각 항목에 대한 설명을 인쇄하면 기본 상태가 예상대로 설정되었음을 보여줍니다.
Failable Initializers
실패할 수 있는 이니셜 라이저
초기화가 실패 할 수 있는 클래스, 구조체 또는 열거형을 정의하는 것이 유용한 경우가 있습니다. 이 실패는 유효하지 않은 초기화 매개 변수 값, 필요한 외부 자원의 부재 또는 초기화 성공을 방해하는 기타 조건에 의해 트리거 될 수 있습니다.
실패 할 수있는 초기화 조건에 대처하려면 하나 이상의 실패 가능한 이니셜 라이저를 클래스, 구조체 또는 열거형 정의의 일부로 정의하십시오. init 키워드 (init?) 뒤에 물음표를 넣어 실패 할 수있는 이니셜 라이저를 작성합니다.
NOTE
동일한 매개 변수 타입 및 이름으로 실패 가능 및 실패 불가능 이니셜 라이저를 정의 할 수 없습니다.
실패 할 수있는 이니셜 라이저는 초기화하는 타입의 옵셔널 값을 생성합니다. 실패 가능한 이니셜 라이저 내에서 return nil을 작성하여 초기화 실패가 트리거 될 수있는 지점을 나타냅니다.
NOTE
엄밀히 말하면 이니셜 라이저는 값을 반환하지 않습니다. 오히려 그들의 역할은 초기화가 끝날 때까지 self가 완전하고 정확하게 초기화되도록하는 것입니다. 초기화 실패를 트리거하기 위해 return nil을 작성하더라도 초기화 성공을 나타 내기 위해 return 키워드를 사용하지 않습니다.
예를 들어, 실패 할 수있는 이니셜 라이저는 숫자 타입 변환을 위해 구현됩니다. 숫자 타입 간의 변환이 값을 정확하게 유지하도록 하기위해 init (exactly : ) 이니셜 라이저를 사용합니다. 타입 변환이 값을 유지할 수 없으면 이니셜 라이저가 실패합니다.
let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print ( "\ (wholeNumber) Int 로의 변환은 \ (valueMaintained)"의 값을 유지합니다. ")
}
// "12345.0을 Int로 변환하면 12345 값이 유지됩니다"를 출력합니다.
let valueChanged = Int(exactly: pi)
// valueChanged는 Int가 아닌 Int? 유형입니다.
if valueChanged == nil {
print ( "\ (pi) Int 로의 변환은 값을 유지하지 않습니다.")
}
// "3.14159 Int 로의 변환이 값을 유지하지 않음"을 인쇄합니다.
아래 예제는 species라는 상수 String 프로퍼티를 사용하여 Animal이라는 구조체를 정의합니다. Animal 구조체는 또한 species라는 단일 매개 변수로 실패 할 수있는 이니셜 라이저를 정의합니다. 이 이니셜 라이저는 이니셜 라이저에 전달 된 species 값이 빈 문자열인지 확인합니다. 빈 문자열이 발견되면 초기화 실패가 트리거됩니다. 그렇지 않으면 species 프로퍼티의 값이 설정되고 초기화가 성공합니다.
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
이 실패 할 수있는 이니셜 라이저를 사용하여 새 Animal 인스턴스를 초기화하고 초기화가 성공했는지 확인할 수 있습니다.
let anonymousCreature = Animal(species: "")
// anonymousCreature는 Animal이 아니라 Animal? 유형입니다.
if anonymousCreature == nil {
print ( "익명 생물을 초기화 할 수 없습니다.")}
// "익명 생물을 초기화 할 수 없음"을 인쇄합니다.
NOTE
빈 문자열 값 (예 : “Giraffe”대신 “”)을 확인하는 것은 옵셔널 문자열 “값이 없음”을 나타내기 위해 nil을 확인하는 것과 다릅니다. 위의 예에서 빈 문자열 ( “”)은 유효하고 옵셔널이 아닌 문자열입니다. 그러나 동물이 species 프로퍼티 값으로 빈 문자열을 갖는 것은 적절하지 않습니다. 이 제한을 모델링하기 위해 실패 가능한 이니셜 라이저는 빈 문자열이 발견되면 초기화 실패를 트리거합니다.
Failable Initializers for Enumerations
열거형에 대한 실패 가능한 이니셜 라이저
실패 가능한 이니셜 라이저를 사용하여 하나 이상의 매개 변수를 기반으로 적절한 열거형 케이스를 선택할 수 있습니다. 이니셜 라이저는 제공된 매개 변수가 적절한 열거형 케이스와 일치하지 않으면 실패 할 수 있습니다.
아래 예제는 세 가지 상태 (켈빈, 섭씨, 화씨)가있는 TemperatureUnit이라는 열거형을 정의합니다. 실패 가능한 이니셜 라이저는 온도 기호를 나타내는 문자 값에 대한 적절한 열거 케이스를 찾는 데 사용됩니다.
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 ( "정의 된 온도 단위이므로 초기화에 성공했습니다.")
}
// "이것은 정의 된 온도 단위이므로 초기화에 성공했습니다."를 인쇄합니다.
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print ( "정의 된 온도 단위가 아니므로 초기화에 실패했습니다.")
}
// "이것은 정의 된 온도 단위가 아니므로 초기화에 실패했습니다."를 인쇄합니다.
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(symbol: "F")
if fahrenheitUnit != nil {
print ( "정의 된 온도 단위이므로 초기화에 성공했습니다.")
}
// "이것은 정의 된 온도 단위이므로 초기화에 성공했습니다."를 인쇄합니다.
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print ( "정의 된 온도 단위가 아니므로 초기화에 실패했습니다.")
}
// "이것은 정의 된 온도 단위가 아니므로 초기화에 실패했습니다."를 인쇄합니다.
Propagation of Initialization Failure
초기화 실패 전파
클래스, 구조체 또는 열거형의 실패 가능한 이니셜 라이저는 동일한 클래스, 구조체 또는 열거형에서 다른 실패 가능한 이니셜 라이저에 위임 할 수 있습니다. 마찬가지로 하위 클래스 실패 가능 이니셜 라이저는 수퍼 클래스 실패 가능 이니셜 라이저까지 위임 할 수 있습니다.
두 경우 모두 초기화 실패를 유발하는 다른 초기화 프로그램에 위임하면 전체 초기화 프로세스가 즉시 실패하고 더 이상 초기화 코드가 실행되지 않습니다.
NOTE
실패 할 수 있는 이니셜 라이저는 실패 할 수 없는 이니셜 라이저에 위임 할 수도 있습니다. 다른 방법으로는 실패하지 않는 기존 초기화 프로세스에 잠재적인 실패 상태를 추가해야하는 경우이 방법을 사용합니다.
아래 예제는 CartItem이라는 Product의 하위 클래스를 정의합니다. CartItem 클래스는 온라인 쇼핑 카트의 항목을 모델링합니다. CartItem은 quantity 이라는 저장된 상수 프로퍼티를 도입하고이 프로퍼티의 값이 항상 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에 대한 실패 가능한 이니셜 라이저는 quantity 값 1 이상을 수신했는지 확인하여 시작합니다. 수량이 유효하지 않으면 전체 초기화 프로세스가 즉시 실패하고 더 이상 초기화 코드가 실행되지 않습니다. 마찬가지로 Product의 실패 가능한 이니셜 라이저는 이름 값을 확인하고 name이 빈 문자열이면 이니셜 라이저 프로세스가 즉시 실패합니다.
비어 있지 않은 name과 quantity이 1 개 이상인 CartItem 인스턴스를 생성하면 초기화가 성공합니다.
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"
quantity 값이 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"
마찬가지로 name 값이 비어있는 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
실패할 수 있는 이니셜 라이저의 재정의
다른 이니셜 라이저와 마찬가지로 하위 클래스에서 실패 할 수있는 수퍼 클래스 이니셜 라이저를 재정의 할 수 있습니다. 또는 하위 클래스 실패 불가능 이니셜 라이저로 수퍼 클래스 실패 가능 이니셜 라이저를 재정의 할 수 있습니다. 이렇게하면 수퍼 클래스 초기화가 실패해도 초기화가 실패 할 수없는 하위 클래스를 정의 할 수 있습니다.
실패 할 수없는 하위 클래스 이니셜 라이저로 실패 할 수있는 슈퍼 클래스 이니셜 라이저를 재정의하는 경우 슈퍼 클래스 이니셜 라이저까지 위임하는 유일한 방법은 실패 할 수있는 슈퍼 클래스 이니셜 라이저의 결과를 강제로 언 래핑하는 것입니다.
NOTE
실패 할 수없는 이니셜 라이저로 실패 할 수있는 이니셜 라이저를 재정의 할 수 있지만 그 반대는 할 수 없습니다.
아래 예제는 Document라는 클래스를 정의합니다. 이 클래스는 비어 있지 않은 문자열 값 또는 nil이지만 빈 문자열 일 수없는 이름 속성으로 초기화 할 수있는 문서를 모델링합니다.
class Document {
var name: String?
//이 이니셜 라이저는 이름 값이 없는 Document를 생성합니다.
init() {}
//이 이니셜 라이저는 비어 있지 않은 이름 값으로 Document를 만듭니다.
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
다음 예제는 AutomaticNamedDocument라는 Document의 하위 클래스를 정의합니다. AutomaticNamedDocument 하위 클래스는 Document에서 도입 한 지정된 이니셜 라이저를 모두 재정의합니다. 이러한 재정의는 인스턴스가 이름없이 초기화되거나 빈 문자열이 init (name : ) 이니셜 라이저에 전달 된 경우 AutomaticallyNamedDocument 인스턴스의 초기 이름 값이 “[Untitled]”임을 보장합니다.
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[제목없음]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[제목없음]"
} else {
self.name = name
}
}
}
AutomaticNamedDocument는 수퍼 클래스의 실패 가능한 init? (name : ) 이니셜 라이저를 실패 할 수 없는 init (name : ) 이니셜 라이저로 대체합니다. AutomaticNamedDocument는 수퍼 클래스와는 다른 방식으로 빈 문자열 대소 문자를 처리하기 때문에 이니셜 라이저는 실패 할 필요가 없으며 대신 실패 할 수 없는 버전의 이니셜 라이저를 제공합니다.
이니셜 라이저에서 강제 언 래핑을 사용하여 하위 클래스의 실패 할 수 없는 이니셜 라이저 구현의 일부로 수퍼 클래스에서 실패 할 수있는 이니셜 라이저를 호출 할 수 있습니다. 예를 들어, 아래의 UntitledDocument 하위 클래스는 항상 “[이름없음]”라는 이름이 지정되며 초기화 중에 수퍼 클래스에서 실패 할 수있는 init (name : ) 이니셜 라이저를 사용합니다.
class UntitledDocument: Document {
override init() {
super.init(name: "[이름없음]")!
}
}
이 경우, 슈퍼 클래스의 init (name : ) 이니셜 라이저가 이름으로 빈 문자열을 사용하여 호출 된 경우 강제 래핑 해제 작업으로 인해 런타임 오류가 발생합니다. 그러나 문자열 상수로 호출되기 때문에 이니셜 라이저가 실패하지 않을 것임을 알 수 있으므로이 경우 런타임 오류가 발생하지 않습니다.
The init! Failable Initializer
init! 실패한 초기화
일반적으로 init 키워드 (init?) 뒤에 물음표를 배치하여 적절한 유형의 옵셔널 인스턴스를 생성하는 실패 가능한 이니셜 라이저를 정의합니다. 또는 적절한 유형의 암시 적으로 래핑되지 않은 옵셔널 인스턴스를 만드는 실패 가능한 이니셜 라이저를 정의 할 수 있습니다. 물음표 대신 init 키워드 (init!) 뒤에 느낌표를 넣으면됩니다.
init? 을 init! 에서 위임 할 수 있고 그 반대의 경우도 마찬가지입니다. init! 을 override init? 그 반대. init에서 init!로 위임 할 수도 있지만 그렇게하면 init! 이니셜 라이저로 인해 초기화가 실패합니다.
Required Initializers
필수 이니셜 라이저
클래스 이니셜 라이저 정의 전에 필요한 수정자를 작성하여 클래스의 모든 하위 클래스가 해당 이니셜 라이저를 구현해야 함을 나타냅니다.
class SomeClass {
required init() {
// 이니셜 라이저 구현은 여기에
}
}
또한 이니셜 라이저 요구 사항이 체인의 추가 하위 클래스에 적용됨을 나타 내기 위해 필수 이니셜 라이저의 모든 서브 클래스 구현 전에 필수 수정자를 작성해야합니다. 지정된 필수 이니셜 라이저를 재정의 할 때 재정의 수정자를 작성하지 않습니다.
class SomeSubclass: SomeClass {
required init() {
// 필수 이니셜 라이저의 서브 클래스 구현이 여기에
}
}
NOTE
상속 된 이니셜 라이저로 요구 사항을 충족 할 수 있는 경우 필수 이니셜 라이저를 명시 적으로 구현할 필요가 없습니다.
Setting a Default Property Value with a Closure or Function
클로저 또는 함수를 사용하여 기본 프로퍼티 값 설정
저장된 프로퍼티의 기본값에 일부 사용자 지정 또는 설정이 필요한 경우 클로저 또는 전역 함수를 사용하여 해당 프로퍼티에 대한 사용자 지정 기본값을 제공 할 수 있습니다. 프로퍼티가 속한 타입의 새 인스턴스가 초기화 될 때마다 클로저 또는 함수가 호출되고 반환 값이 프로퍼티의 기본값으로 할당됩니다.
이러한 종류의 클로저 또는 함수는 일반적으로 프로퍼티와 동일한 타입의 임시 값을 만들고 원하는 초기 상태를 나타내도록 해당 값을 조정 한 다음 프로퍼티의 기본값으로 사용 할 임시 값을 반환합니다.
다음은 기본 프로퍼티 값을 제공하기 위해 클로저를 사용하는 방법에 대한 개요입니다.
class SomeClass {
let someProperty: SomeType = {
// 이 클로저 안에 someProperty에 대한 기본값을 만듭니다.
// someValue는 SomeType과 동일한 유형이어야합니다.
return someValye
}()
}
클로저의 끝 중괄호 뒤에는 빈 괄호 쌍이 옵니다. 이것은 Swift가 클로저를 즉시 실행하도록합니다. 이러한 괄호를 생략하면 클로저의 반환 값이 아니라 클로저 자체를 프로퍼티에 할당하려고합니다.
NOTE
클로저를 사용하여 프로퍼티를 초기화하는 경우 나머지 인스턴스는 클로저가 실행되는 시점에서 아직 초기화되지 않았습니다. 즉, 해당 프로퍼티에 기본값이 있더라도 클로저 내에서 다른 프로퍼티 값에 액세스 할 수 없습니다. 암시 적 자체 프로퍼티를 사용하거나 인스턴스의 메서드를 호출 할 수도 없습니다.
아래 예는 체스 게임용 보드를 모델링하는 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의 기본값이 계산되어 반환됩니다. 위 예제의 클로저는 temporaryBoard라는 임시 배열에서 보드의 각 사각형에 대해 적절한 색상을 계산하고 설정하며, 설정이 완료되면이 임시 배열을 클로저의 반환 값으로 반환합니다. 반환 된 배열 값은 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"