You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
초기화(Initialization)은 클래스, 구조체, 열거형의 인스턴스를 사용하기 위해 준비하는 과정
각각의 저장 프로퍼티에 초기 값을 설정하고,
새로운 인스턴스의 사용이 준비되기 전에 필요한 다른 세팅이나 초기화 작업을 실행
이니셜라이저(initializer)를 정의해서 초기화 과정을 구현
특정 타입의 새로운 인스턴스를 생성하기 위해 호출할 수 있는 특별한 메소드와 흡사
Objective-C의 이니셜라이저와 달리 Swift의 이니셜라이저는 값을 반환하지 않는다
이니셜라이저의 가장 큰 역할은 타입의 새로운 인스턴스가 처음으로 사용되기 전에 정확히 초기화됐는지를 보장
클래스 타입의 인스턴스는 디이니셜라이저(deinitializer)를 구현할 수도 있다
클래스의 인스턴스가 할당을 해제하기 전에 메모리 정리를 하는 것
저장 프로퍼티에 초기 값 설정하기
클래스와 구조체는 인스턴스가 생성되는 시점에
반드시 모든 저장 프로퍼티에 적당한 초기값을 설정
저장 프로퍼티는 미결정된 상태로 남겨질 수 없다
이니셜라이저를 통해 저장 프로퍼티에 값을 설정하거나
프로퍼티 정의의 일부분으로써 프로퍼티 기본 값을 할당 가능
저장 프로퍼티에 기본 값을 할당하거나 이니셜라이저로 초기 값을 설정할 때,
프로퍼티의 값은 프로퍼티 옵저버를 호출하지 않고 직접 설정
1) 이니셜라이저 (Initializer)
이니셜라이저(initializer)는 특정 타입의 새로운 인스턴스를 생성
가장 간단한 형태의 이니셜라이저는 init 키워드를 매개 변수가 없는 인스턴스 메소드처럼 작성
init(){// perform some initialization here}
// 화씨 온도를 저장하기 위한 Fahrenheit 구조체를 정의structFahrenheit{// 저장 프로퍼티인 temperaturevartemperature:Double// init 키워드로 매개 변수가 없는 하나의 구조체를 정의init(){// temperature 프로퍼티를 32.0으로 초기화
temperature = 32.0
}}varf=Fahrenheit()print("The default temperature is \(f.temperature)° Fahrenheit")// Prints "The default temperature is 32.0° Fahrenheit"
2) 프로퍼티 기본 값
이니셜라이저에서 저장 프로퍼티에 초기 값을 설정하는 대신
프로퍼티 선언에서 기본 값을 할당 가능
프로퍼티가 정의될 때 초기 값을 기본 프로퍼티 값으로써 할당
만약 프로퍼티가 항상 같은 초기 값을 가진다면,
기본 값을 제공하는 것이 이니셜라이저를 사용하는 것보다 낫다
결과는 동일하지만, 기본 값을 제공하는 건 이니셜라이저 내부의 할당보다 프로퍼티의 초기화에 더 밀접하게 연결되어 있기 때문
더 짧고 명확하며, 기본 값으로부터 프로퍼티의 타입을 추론할 수 있게 해 준다
기본 값은 또한 기본 이니셜라이저와 이니셜라이저 상속의 이점을 더 쉽게 가져 갈 수 있게 만든다
// Fahrenheit 구조체를 기본 값을 사용하여 더 단순하게 구성 가능structFahrenheit{vartemperature= 32.0
}
커스터마이징 초기화
입력 매개 변수와 옵셔널 프로퍼티 타입을 사용하거나,
초기화 중에 상수 프로퍼티를 할당하여 초기화 과정을 커스터마이징 가능하다
1) 초기화 매개 변수
초기화 정의의 일부분으로써 초기화 과정을 커스터마이징 하는 값의 이름과 타입을 정의하기 위해
초기화 매개 변수(initialization parameter)를 제공 가능
초기화 매개 변수는 함수 / 메서드의 매개 변수 문법과 같은 기능을 가진다
// 섭씨 온도를 저장하는 Celsius 구조체를 정의// init(fromFahrenheit:)와 init(fromKelvin:), 두 개의 커스텀 이니셜라이저를 구현structCelsius{vartemperatureInCelsius:Double// fromFahrenheit 인수 라벨이 붙은, fahrenheit 매개 변수를 가짐init(fromFahrenheit fahrenheit:Double){
temperatureInCelsius =(fahrenheit - 32.0)/ 1.8
}// fromKelvin 인수 라벨이 붙은, kelvin 매개 변수를 가짐init(fromKelvin kelvin:Double){
temperatureInCelsius = kelvin - 273.15
}}letboilingPointOfWater=Celsius(fromFahrenheit: 212.0)// boilingPointOfWater.temperatureInCelsius is 100.0letfreezingPointOfWater=Celsius(fromKelvin: 273.15)// freezingPointOfWater.temperatureInCelsius is 0.0
두 이니셜라이저는 하나의 인자를 일치하는 Celsius 값으로 변환하고,
temperatureInCelsius 프로퍼티에 값을 저장
2) 매개 변수 이름과 인수 라벨
함수와 메서드 매개 변수처럼
초기화 매개 변수도 이니셜라이저의 바디에서 사용하는 매개 변수와
이니셜라이저를 호출할 때 사용하는 인수 라벨을 가질 수 있다
하지만, 이니셜라이저는 함수나 메서드처럼 식별 함수 이름이 없다
이니셜라이저 매개 변수의 이름과 타입은 어떤 이니셜라이저가 호출되는지 식별할 때 중요한 역할
때문에 Swift는 인수 라벨을 붙이지 않은 모든 매개 변수에 자동으로 인수 라벨을 제공
// 세 상수 프로퍼티를 갖고 있는 Color 구조체를 정의// 프로퍼티는 0.0에서 1.0 사이의 부동 소수점값을 저장structColor{letred,green,blue:Double// Double 타입의 매개 변수 세 개(red, green, blue)가 있는 이니셜라이저init(red:Double, green:Double, blue:Double){self.red = red
self.green = green
self.blue = blue
}// 하나의 매개 변수(white)를 갖는 이니셜라이저init(white:Double){
red = white
green = white
blue = white
}}// 각 이니셜라이저 매개 변수에 이름과 함께 값을 제공함으로써 새로운 Color 인스턴스를 생성// 인수 라벨 없이 이니셜라이저를 호출할 수는 없다. 인수 라벨은 반드시 이니셜라이저에서 사용letmagenta=Color(red: 1.0, green: 0.0, blue: 1.0)lethalfGray=Color(white: 0.5)// 생략할 경우 컴파일 에러letveryGreen=Color(0.0, 1.0, 0.0)// this reports a compile-time error - argument labels are required
3) 인수 라벨이 없는 이니셜라이저 매개 변수
만약 이니셜라이저 매개 변수에 인수 라벨을 사용하기 싫다면
언더스코어(_)를 인수 라벨 대신 사용 가능
structCelsius{vartemperatureInCelsius:Doubleinit(fromFahrenheit fahrenheit:Double){
temperatureInCelsius =(fahrenheit - 32.0)/ 1.8
}init(fromKelvin kelvin:Double){
temperatureInCelsius = kelvin - 273.15
}// 언더스코어(_)를 통한 인수 라벨 생략init(_ celsius:Double){
temperatureInCelsius = celsius
}}// Celcius(37.0)는 인수 라벨 없이 의도가 명확, 이름 없는 Double 값을 이니셜라이저에 제공하여 호출letbodyTemperature=Celsius(37.0)// bodyTemperature.temperatureInCelsius is 37.0
4) 옵셔널 프로퍼티 타입
만약 커스텀 타입이 초기화 과정 중 할당되지 않을 수도 있는 값이라, 혹은 나중에 값이 할당되기 때문에
nil 상태를 허용한다면, 프로퍼티를 옵셔널 타입으로 선언
옵셔널 타입의 프로퍼티는 자동적으로 nil 값으로 초기화
// 옵셔널 프로퍼티 response를 갖는 클래스 SurveyQuestion를 정의classSurveyQuestion{vartext:Stringvarresponse:String?init(text:String){self.text = text
}func ask(){print(text)}}letcheeseQuestion=SurveyQuestion(text:"Do you like cheese?")
cheeseQuestion.ask()// Prints "Do you like cheese?"
cheeseQuestion.response ="Yes, I do like cheese."
5) 초기화 중에 상수 프로퍼티를 할당하기
초기화할 때 상수 프로퍼티에도 값을 할당 가능
한번 상수 프로퍼티에 값을 할당하면, 더 이상 수정 불가
클래스 인스턴스의 경우 상수 프로퍼티는 해당 프로퍼티를 도입한 클래스에 의해서 초기화 중에만 수정 가능
자식 클래스에서는 수정 불가
// SurveyQuestion을 상수 프로퍼티를 사용하도록 변경한 예classSurveyQuestion{lettext:Stringvarresponse:String?init(text:String){// text 프로퍼티가 상수임에도 불구하고, 클래스의 이니셜라이저에서 수정 가능self.text = text
}func ask(){print(text)}}letbeetsQuestion=SurveyQuestion(text:"How about beets?")
beetsQuestion.ask()// Prints "How about beets?"
beetsQuestion.response ="I also like beets. (But not with cheese.)"
기본 이니셜라이저 (Default Initializer)
구조체나 클래스의 모든 프로퍼티가 기본 값을 갖고 있는 상황에서 이니셜라이저를 정의하지 않는다면,
Swift는 기본 이니셜라이저(default initializer)를 제공
기본 이니셜라이저는 모든 프로퍼티를 기본 값으로 초기화
// ShoppingListItem 클래스를 정의// 쇼핑 리스트에 있는 물건의 이름, 수량, 구매 상태를 캡슐화classShoppingListItem{varname:String?varquantity=1varpurchased= false
}varitem=ShoppingListItem()
모든 프로퍼티가 기본 값을 갖고 있으면서 부모 클래스가 없는 기반 클래스이기 때문에,
ShoppingListItem는 자동적으로 기본 이니셜라이저를 얻게 된다
name 프로퍼티는 옵셔널 문자열 프로퍼티여서 기본 값을 작성하지 않는 경우 nil
ShoppingListItem()과 같이 새로운 인스턴스를 생성할 때 기본 이니셜라이저가 사용
1) 구조체 타입을 위한 멤버 이니셜라이저
만약 구조체가 다른 커스텀 이니셜라이저를 정의하지 않는다면,
멤버 이니셜라이저(memberwise initializer)를 자동으로 얻는다
클래스와는 달리 구조체는 저장 프로퍼티가 기본 값을 갖지 않아도 멤버 이니셜라이저를 얻는 게 가능
멤버 이니셜라이저는 새로운 구조체 인스턴스의 멤버 프로퍼티를 초기화하는 방법을 축약한 것
새로운 인스턴스의 프로퍼티가 갖는 초기 값은 이름에 의해 멤버 이니셜라이저에 넣어질 수 있다
// width와 height 두 프로퍼티를 갖는 Size 구조체를 정의// Size 구조체는 자동적으로 멤버 이니셜라이저인 init(width:height:)를 얻게 된다 structSize{// 두 프로퍼티는 기본 값 0.0을 할당varwidth= 0.0,height= 0.0
}lettwoByTwo=Size(width: 2.0, height: 2.0)// 멤버 이니셜라이저를 호출할 때, 기본 값을 갖는 프로퍼티의 값을 생략 가능// 이니셜라이저는 초기화할 때 전달된 인수가 없는 경우, 생략된 프로퍼티의 기본 값을 사용letzeroByTwo=Size(height: 2.0)print(zeroByTwo.width, zeroByTwo.height)// Prints "0.0 2.0"letzeroByZero=Size()print(zeroByZero.width, zeroByZero.height)// Prints "0.0 0.0"
값 타입을 위한 이니셜라이저 위임
이니셜라이저는 인스턴스 초기화의 일부분을 수행하기 위해
다른 이니셜라이저를 호출 가능
이니셜라이저 위임(initializer delegation)이라 알려진 과정은 다수의 이니셜라이저 간에 코드의 중복을 막을 수 있다
이니셜라이저 위임이 작동하는 방식에 대한 규칙과 어떤 위임 형태가 혀용되는지는 값 타입과 클래스 타입이 다르다
값 타입(구조체와 열거형)은 상속을 지원하지 않고,
자체적으로 제공하는 다른 이니셜라이저에만 위임이 가능하다
따라서 이니셜라이저 위임 과정은 상대적으로 간단
반면 클래스는 다른 클래스로부터 상속을 받을 수 있다
이는 클래스는 추가적인 책임을 갖고 있다는 것을 뜻한다
상속한 모든 저장 프로퍼티가 초기화 과정에서 적당한 값을 할당받았는지 보장해야 한다
값 타입은 커스텀 이니셜라이저를 작성할 때 self.init을 사용하여
같은 값 타입으로부터의 다른 이니셜라이저를 참조 가능
이니셜라이저 안에서만 self.init을 호출할 수 있다
만약 값 타입을 위한 커스텀 이니셜라이저를 정의한다면,
그 타입의 기본 이니셜라이저(또는 구조체의 경우, 멤버 이니셜라이저)에 더 이상 접근이 불가하며
이러한 제약은 더 복잡한 이니셜라이저에 의해 제공된 필수적인 추가 셋업이
자동으로 생성된 이니셜라이저를 사용하는 무언가에 의해 순환참조되는 것을 막는다
만약 커스텀 값 타입을 기본 이니셜라이저, 멤버 이니셜라이저, 커스텀 이니셜라이저와 함께 초기화하고 싶다면,
값 타입의 원래 구현이 아닌 확장(extension)에서 커스텀 이니셜라이저를 작성하면 된다
// 기하학적 사각형을 표현하는 Rect 구조체를 정의// Size와 Point라는 두 가지 추가적인 구조체를 필요structSize{varwidth= 0.0,height= 0.0
}structPoint{varx= 0.0,y= 0.0
}// 세 가지 방법으로 초기화structRect{varorigin=Point()varsize=Size()// 0으로 초기화된 origin과 size 프로퍼티 값을 사용init(){}// 특정한 origin point와 size 를 제공init(origin:Point, size:Size){self.origin = origin
self.size = size
}// 특정한 center point와 size를 제공init(center:Point, size:Size){letoriginX= center.x -(size.width /2)letoriginY= center.y -(size.height /2)self.init(origin:Point(x: originX, y: originY), size: size)}}// 첫 번째 이니셜라이저인 init()은 기본 이니셜라이저와 같은 기능letbasicRect=Rect()// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)// 두 번째 이니셜라이저인 init(origin:size:)은 멤버 이니셜라이저와 같은 기능letoriginRect=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)// 세 번째 이니셜라이저인 init(center:size:)은 center 포인트와 size 값에 기반하여 원래 포인트를 계산// 그리고 self.init(origin:size) 이니셜라이저를 호출 / 위임한다letcenterRect=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:) 이니셜라이저는 origin과 size의 새로운 값을 적당한 프로퍼티에 직접 할당할 수도 있지만
기존 이니셜라이저를 사용하여 위임하는게 더 편하고 명확히 의도를 드러낼 수 있다
init() 및 init(origin:size:) 이니셜라이저를 직접 정의하지 않고 예제의 구조체를 extension하여 구성할수도 있다
모든 클래스의 저장 프로퍼티는 반드시 초기화 과정에서 초기 값을 할당 받아야 한다
부모 클래스에서 상속 받은 프로퍼티도 마찬가지
Swift는 클래스 타입을 위한 두 종류의 이니셜라이저를 정의하며
모든 저장 프로퍼티가 초기 값을 받는 걸 보장하는 데 도움을 준다
1) 지정 이니셜라이저와 편리한 이니셜라이저 (Designed Initializers and Convenience Initializers)
지정 이니셜라이저(designed initializer)는 클래스의 메인 이니셜라이저
지정 이니셜라이저는 클래스의 모든 프로퍼티를 완전히 초기화
그리고 부모 클래스의 이니셜라이저를 호출하여 부모 클래스 연쇄를 통한 초기화 과정을 계속 진행
클래스는 최소한의 지정 이니셜라이저를 갖도록 의도된다
일반적으로 하나의 지정 이니셜라이저를 갖으며
지정 이니셜라이저는 초기화가 발생하고 부모 클래스 체인으로 이어지는 "깔대기" 포인트와 같다
모든 클래스는 적어도 하나의 지정 이니셜라이저를 가져야 한다
부모 클래스로부터 하나 이상의 지정 이니셜라이저를 상속받아야 하는 경우도 있다
편리한 이니셜라이저(convenience initializer)는 클래스의 이니셜라이저를 지원하는 서브 이니셜라이저다
편리한 이니셜라이저를 정의하여 편리한 이니셜라이저와 동일한 클래스의 지정 이니셜라이저를 호출 가능하며
편리한 이니셜라이저로 지정 이니셜라이저의 매개변수에 기본 값을 설정한다
또한 편리한 이니셜라이저를 정의하여 특정 사용 사례 또는 입력 값 타입에 대해 해당 클래스의 인스턴스를 만드는 것도 가능
만약 클래스에서 필요하지 않다면 편리한 이니셜라이저를 만들 필요가 없다
공통적인 초기화 패턴에 대한 축약이 시간을 절약하거나
클래스 초기화의 의도를 더 명확하게 만들 때마다 편리한 이니셜라이저를 만들면 된다
2) 지정 이니셜라이저와 편리한 이니셜라이저 문법
지정 이니셜라이저는 값 타입의 이니셜라이저와 같은 방식으로 쓰여진다
init(parameters){
statements
}
편리한 이니셜라이저는 같은 스타일이지만, convenience 키워드를 init 앞에 붙인다
convenienceinit(parameters){
statements
}
3) 클래스 타입을 위한 이니셜라이저 위임
지정 이니셜라이저와 편리한 이니셜라이저의 관계를 간단히 하기 위해,
Swift는 이니셜라이저 간 위임 호출이 일어날 때 다음 세 가지 규칙을 적용
지정 이니셜라이저는 직계 부모 클래스의 지정 이니셜라이저를 반드시 호출해야 한다
편리한 이니셜라이저는 같은 클래스의 다른 이니셜라이저를 반드시 호출해야 한다
편리한 이니셜라이저는 궁극적으로 지정 이니셜라이저를 반드시 호출해야 한다
간단히 말하자면 이렇다.
지정 이니셜라이저는 반드시 부모 클래스를 위임한다
편리한 이니셜라이저는 반드시 같은 레벨을 위임한다
이미지로 표현하면 다음과 같다.
여기서 부모클래스는 하나의 지정 이니셜라이저와 두 개의 편리한 이니셜라이저를 가지고 있다
하나의 편리한 이니셜라이저는 다른 편리한 이니셜라이저를 호출하고, 이는 다시 단일 지정 이니셜라이저를 호출
이 건 규칙 2와 3을 만족한다
부모클래스 자체에는 더 이상의 슈퍼클래스가 없으므로 규칙 1은 적용되지 않는다
그림의 자식 클래스에는 두 개의 지정 이니셜라이저와 한 개의 편리한 이니셜라이저가 있다
편리한 이니셜라이저는 동일한 클래스의 다른 이니셜라이저만 호출할 수 있으므로
두 개의 지정 이니셜라이저 중 하나를 호출해야 한다
이 건 위에서부터 규칙 2와 3을 만족한다
두 지정 이니셜라이저 모두 부모클래스의 단일 지정 이니셜라이저를 호출하여 위의 규칙 1을 충족하게 된다
해당 규칙은 클래스의 사용자가 각 클래스의 인스턴스를 만드는 방법에는 아무 영향을 끼치지 않는다
그림에서 어떤 이니셜라이저를 사용해도 인스턴스를 구성 가능하다
클래스 이니셜라이저의 구현을 어떻게 작성할 것인지에만 영향을 끼친다
위의 그림은 4개 클래스에 대한 더 복잡한 클래스 계층을 보여준다
계층에서 지정 이니셜라이저가 클래스 초기화를 위한 "깔대기" 포인트로 작용하여
체인의 클래스 간의 상호 관계를 단순화하는 방법을 나타낸다
4) 2단계 초기화 (Two-Phase Initialization)
Swift에서 클래스 초기화는 2단계로 진행된다
첫 번째 단계는 각 저장 프로퍼티는 클래스에서 정한 초기값으로 초기화
모든 저장 프로퍼티의 상태가 결정되면 두 번째 단계가 시작
두 번째 단계는 새로운 인스턴스의 사용이 준비됐다고 알려주기 전에 저장 프로퍼티를 커스터마이징하는 단계
2단계 초기화의 사용은 클래스 상속에서 각 클래스에 완전한 융통성을 주면서도 초기화를 안전하게 만들어 준다
2단계 초기화는 초기화되기 전에 값에 접근되는 것을 방지
예상치 못한 다른 이니셜라이저에 의해 다른 값이 프로퍼티 값에 설정되는 것을 예방
Swift의 2단계 초기화는 Objective-C에서의 초기화와 유사하다
주된 차이점은 첫 번째 단계로
Objective-C에서는 모든 프로퍼티에 오직 0 혹은 nil 값만 할당 가능
Swift의 초기화는 좀 더 유연해서 커스텀한 초기 값을 할당 가능, 그리고 0과 nil이 잘못된 초기값으로 지정된 경우 대처 가능
Swift의 컴파일러는 2단계 초기화가 에러없이 끝나는 것을 보장하기 위해 4단계 안전 확인(safety-check)을 한다
안전 확인 1단계
지정 이니셜라이저는 클래스의 모든 프로퍼티가 부모 클래스의 이니셜라이저에 위임하기 전에 초기화됐음을 보장해야 한다. 객체의 메모리는 모든 저장 프로퍼티의 초기 상태가 확인돼야만 완전히 초기화된 것으로 간주한다. 규칙이 충족되려면 지정 이니셜라이저가 체인을 넘겨주기 전에 자신의 모든 프로퍼티가 초기화되었는지 확인해야 한다.
안전 확인 2단계
지정 이니셜라이저는 반드시 상속한 프로퍼티에 값을 할당하기 전에 부모 클래스의 이니셜라이저로 위임해야 한다. 그렇지 않으면 지정 이니셜라이저가 할당한 새로운 값은 부모 클래스에 의해 덮어 쓰여진다.
안전 확인 3단계
편리한 이니셜라이저는 어떤 프로퍼티에 값을 할당하기 전에 반드시 다른 이니셜라이저에 위임해야 한다. 그렇지 않으면 편리한 이니셜라이저가 할당한 새로운 값은 클래스의 지정 이니셜라이저에 의해 덮어 쓰여진다.
안전 확인 4단계
이니셜라이저는 초기화의 1단계가 끝나기 전까지 다른 인스턴스 메소드를 호출하거나, 인스턴스 프로퍼티의 값을 읽거나, 값으로써 self를 참조할 수 없다.
클래스 인스턴스는 1단계가 끝나기 전까지는 완전히 유효하지 않다
1단계 종료 시점부터 프로퍼티에 접근하거나, 메소드를 호출할 수 있고, 클래스 인스턴스가 유효해진다
4단계 안전 확인에 기반하여 2단계 초기화는 다음과 같이 작동한다.
1단계
지정/편리한 이니셜라이저가 클래스에서 호출된다.
클래스의 새로운 인스턴스를 위한 메모리가 할당된다. 이 메모리는 아직 초기화되지 않았다.
지정 이니셜라이저는 모든 저장 프로퍼티가 값을 갖고 있는지 확인한다. 이 저장 프로퍼티들의 메모리는 아직 초기화되지 않았다.
지정 이니셜라이저는 부모 클래스의 이니셜라이저로 넘기고, 같은 작업을 실시한다.
최상단 클래스에 도착할 때까지 클래스 상속 연쇄를 계속 진행한다.
최상단 클래스에 도착하고, 연쇄의 마지막 클래스가 모든 저장 프로퍼티의 값 보유를 보장한다면, 인스턴스의 메모리는 완전히 초기화된 것으로 간주한다. 1단계가 완료된다.
2단계
최상단 클래스부터 밑으로 작업을 실시한다. 각 지정 이니셜라이저는 인스턴스를 커스터마이징 할 옵션을 갖고 있다. 이니셜라이저는 이제 self에 접근 가능하며, 그것의 프로퍼티를 수정하고 인스턴스 메소드를 호출할 수 있다.
최종적으로 연쇄 안의 모든 편리한 이니셜라이저는 인스턴스를 커스터마이징 하고 self를 사용할 수 있는 옵션을 갖게 된다.
2단계 초기화를 도식화하면 다음과 같다
1단계에서 자식 클래스 및 부모 클래스에 대한 초기화 호출을 찾는 방법
초기화는 자식 클래스의 편리한 이니셜라이저에 대한 호출로 시작
편리한 이니셜라이저는 아직 프로퍼티를 수정할 수 없다
해당 작업은 같은 클래스의 지정 이니셜라이저에게 위임
지정 이니셜라이저는 안전 확인 1에 따라 자식 클래스의 모든 프로퍼티에 값이 있는지 확인
그런 다음 부모 클래스의 지정 이니셜라이저를 호출하여 체인을 따라 초기화를 계속 진행
부모 클래스의 지정 이니셜라이저는 모든 부모 클래스 프로퍼티에 값이 있는지 확인
더 이상 초기화할 부모 클래스가 없으므로(기반 클래스) 더 이상의 위임이 필요하지 않다
부모 클래스의 모든 프로퍼티가 초기값을 가지는 즉시 메모리가 완전히 초기화된 것으로 간주되며 1단계는 완료
2단계에서 동일한 초기화 호출을 찾는 방법
1단계 완료 후, 부모 클래스의 지정 이니셜라이저는 이제 인스턴스를 추가적인 커스텀 작업을 수행 가능
부모 클래스의 지정 이니셜라이저가 완료되면 자식 클래스의 지정 이니셜라이저가 추가적인 커스텀 작업을 수행 가능
(추가적인 커스텀은 선택적인 사항이기에 반드시 추가적으로 수행할 필요는 없음)
마지막으로 자식 클래스의 지정 이니셜라이저가 완료되면 원래 처음 호출된 편의 이니셜라이저가 추가 커스터마이징을 수행 가능
5) 이니셜라이저 상속과 오버라이딩
Swift의 자식 클래스는 Objective-C의 자식 클래스와는 달리, 부모 클래스의 이니셜라이저를 기본 값으로 상속하지 않는다
Swift의 접근 방식은 부모 클래스의 간단한 이니셜라이저가 보다 복잡한 자식 클래스에 상속되어
완전히 또는 올바르게 초기화되지 않은 자식 클래스의 새 인스턴스를 생성하는 상황을 방지한다
부모 클래스 이니셜라이저는 특정 상황에서 상속되며, 안전하고 적절한 경우에만 상속된다
만약 부모 클래스와 동일한 이니셜라이저를 자식 클래스에서 만들고자 한다면,
자식 클래스에서 이런 이니셜라이저의 커스텀 작업을 구현하면 된다
부모 클래스의 지정 이니셜라이저와 매치되는 자식 클래스 이니셜라이저를 만들 때,
지정 이니셜라이저를 오버라이드 가능
따라서, 자식 클래스의 이니셜라이저를 정의할 때 override 키워드를 붙여야 한다
자동으로 제공된 기본 이니셜라이저를 오버라이딩 할 때도 마찬가지
오버라이드된 프로퍼티, 메소드, 서브스크립트처럼 override 키워드의 존재는
Swift에게 부모 클래스가 매치되는 지정 이니셜라이저를 갖고 있는지,
오버라이딩 하는 이니셜라이저의 매개 변수는 유효한지 확인하게 한다
부모 클래스의 지정 이니셜라이저를 오버라이딩 할 때는
자식 클래스에서 구현하는 이니셜라이저가 편리한 이니셜라이저이더라도 항상 override 키워드를 작성해야 한다
거꾸로, 만약 부모 클래스의 편리한 이니셜라이저와 매치되는 자식 클래스의 이니셜라이저를 작성한다면,
부모 클래스의 편리한 이니셜라이저는 위에서 언급한 이니셜라이저 위임 규칙 때문에
자식 클래스에 의해 절대 직접적으로 호출되지 않는다.
그러므로, 자식 클래스는 부모 클래스의 편리한 이니셜라이저를 오버라이딩 할 수 없다
결과적으로, 부모 클래스의 편리한 이니셜라이저와 매치되는 이니셜라이저를 구현할 때는 override 키워드를 쓰면 안 된다
이 기반 클래스는 기본 값이 0인 numberOfWheels 저장 프로퍼티를 선언한다. numberOfWheels 프로퍼티는 계산 프로피티인 description에서 차량의 특성을 묘사하기 위해 사용된다.
// Vehicle 기반 클래스를 정의// numberOfWheels 저장 프로퍼티에만 기본 값 0을 제공하고, 별다른 커스텀 이니셜라이저가 없다// 기본 이니셜라이저가 자동으로 생성// 항상 지정 이니셜라이저로 numberOfWheels가 0인 새로운 Vehicle 인스턴스를 만드는 데 사용classVehicle{varnumberOfWheels=0vardescription:String{return"\(numberOfWheels) wheel(s)"}}letvehicle=Vehicle()print("Vehicle: \(vehicle.description)")// Vehicle: 0 wheel(s)// Vehicle을 상속한 Bicycle을 정의// Bicycle 자식 클래스는 커스텀 지정 연산자인 init()을 정의// 지정 이니셜라이저는 부모 클래스의 지정 이니셜라이저와 매치// Bicycle 버전임을 표시하기 위해 override 키워드 삽입classBicycle:Vehicle{overrideinit(){// super.init()을 통해 Bicycle의 부모 클래스로부터 기본 클래스를 호출// numberOfWheels 프로퍼티가 Vehicle에서 초기화되었음을 보장
super.init()
numberOfWheels =2}}letbicycle=Bicycle()// Bicycle의 인스턴스를 만든다면 상속된 계산 프로퍼티인 description을 호출 가능print("Bicycle: \(bicycle.description)")// Bicycle: 2 wheel(s)
만약 자식 클래스의 이니셜라이저가 2번째 초기화 단계에서 아무런 커스터마이징도 하지 않고,
부모 클래스가 인자 없는 이니셜라이저를 갖고 있다면, super.init()을 생략 가능
// Vehicle의 자식 클래스인 Hoverboard를 정의classHoverboard:Vehicle{varcolor:String// 이니셜라이저에서 Hoverboard 클래스는 color 프로퍼티에만 값을 할당init(color:String){self.color = color
// super.init() implicitly called here// 명시적으로 super.init()을 호출하는 대신 이 이니셜라이저는 암시적 호출에 의존}overridevardescription:String{return"\(super.description) in a beautiful \(color)"}}lethoverboard=Hoverboard(color:"silver")print("Hoverboard: \(hoverboard.description)")// Hoverboard: 0 wheel(s) in a beautiful silver// Hoverboard의 인스턴스는 Vehicle 이니셜라이저에서 지원되는 numberOfWheels 기본 값을 사용
자식 클래스는 초기화 도중 상속한 변수 프로퍼티를 수정할 수 있지만, 상수 프로퍼티를 수정할 수는 없다
6) 자동 이니셜라이저 상속
자식 클래스는 부모 클래스의 이니셜라이저를 기본으로 상속하지는 않는다
하지만, 특정한 조건을 충족한다면 부모 클래스의 이니셜라이저가 자동으로 상속
사실 많은 상황에서 직접 이니셜라이저를 오버라이드 할 필요는 없으며
안전할 때만 최소한의 노력으로 부모 클래스 이니셜라이저를 상속 가능
자식 클래스에서 추가한 모든 프로퍼티에 기본 값을 제공하면 다음 두 가지 규칙이 적용된다
규칙 1
만약 자식 클래스가 지정 이니셜라이저를 정의하지 않았다면, 자동으로 부모 클래스의 모든 지정 이니셜라이저를 상속한다.
규칙 2
자식 클래스가 규칙 1에 따라 상속하거나 정의의 일부로 커스텀을 통하여 모든 부모 클래스 지정 이니셜라이저를 구현하는 경우
모든 부모 클래스 편의 이니셜라이저를 자동으로 상속한다.
이러한 규칙은 자식 클래스가 편리한 이니셜라이저를 추가하더라도 적용된다.
규칙 2에 따라 자식 클래스는 부모 클래스의 지정 이니셜라이저를 자식 클래스의 편리한 이니셜라이저로 구현 가능
7) 지정 / 편리한 이니셜라이저의 사용
다음 예제에서는 지정 이니셜라이저, 편리한 이니셜라이저 및 자동으로 이니셜라이저 상속이 수행되고 있음을 보여준다
Food, RecipeIngredient, ShoppingListItem 클래스의 계층과 이니셜라이저가 상호작용하는 방식을 정의
// Food는 계층의 기반 클래스// 클래스이기에 기본 멤버 이니셜라이저를 갖고 있지 않으며,// 하나의 인자(name)을 갖는 지정 이니셜라이저를 제공classFood{varname:Stringinit(name:String){self.name = name
}convenienceinit(){self.init(name:"[Unnamed]")}}letnamedMeat=Food(name:"Bacon")// namedMeat's name is "Bacon"letmysteryMeat=Food()// mysteryMeat's name is "[Unnamed]"
새로운 Food 인스턴스의 모든 저장 프로퍼티가 완전히 초기화되는 것을 보장하기 때문에
init(name: String) 이니셜라이저는 지정 이니셜라이저가 된다
Food 클래스는 부모 클래스가 없기 때문에 super.init()을 호출할 필요가 없다
Food 클래스는 인자가 없는 편리한 이니셜라이저인 init()을 제공한다
init() 이니셜라이저는 init(name: String)에 [Unnamed]를 name 값으로 하여 위임
// 계층의 두 번째 클래스는 Food의 자식 클래스인 RecipeIngredient// quantity 저장 프로퍼티를 선언// 두 개의 이니셜라이저를 정의classRecipeIngredient:Food{varquantity:Intinit(name:String, quantity:Int){self.quantity = quantity
super.init(name: name)}overrideconvenienceinit(name:String){self.init(name: name, quantity:1)}}letoneMysteryItem=RecipeIngredient()letoneBacon=RecipeIngredient(name:"Bacon")letsixEggs=RecipeIngredient(name:"Eggs", quantity:6)
RecipeIngredient은 하나의 지정 이니셜라이저인 init(name: String, quantity: Int)을 갖는다
모든 프로퍼티를 채우는 데 사용
해당 이니셜라이저는 quantity 인자를 quantity 프로퍼티에 할당
그런 뒤에 Food 클래스의 init(name: String)에 이니셜라이저를 위임
해당 과정은 2단계 초기화의 안전 확인 1단계를 만족
RecipeIngredient은 편리한 이니셜라이저인 init(name: String)도 정의한다
해당 편리한 이니셜라이저는 명시적 quantity가 없이
생성된 모든 RecipeIngredient의 인스턴스는 quantity가 1이라고 가정
편리한 이니셜라이저는 RecipeIngredient의 인스턴스를 더 빠르고 편리하게 만들 수 있게 구성
quantity가 1인 RecipeIngredient 인스턴스를 여러 개 만들 때 코드의 중복을 예방 가능
init(name: String) 편리한 이니셜라이저는 Food의 init(name: String) 지정 이니셜라이저와 같은 매개 변수를 취한다
해당 편리한 이니셜라이저가 부모 클래스의 지정 이니셜라이저를 오버라이드 하기 때문에
override 키워드를 표시
RecipeIngredient의 init(name: String)가 편리한 이니셜라이저이긴 하지만,
RecipeIngredient는 부모 클래스의 모든 이니셜라이저를 구현하고 있다
따라서, RecipeIngredient는 부모 클래스의 편리한 이니셜라이저 역시 자동으로 상속 가능
또한, Food의 편리한 이니셜라이저인 init() 역시 사용 가능
// 계층의 세 번째이자 마지막 클래스는 RecipeIngredient의 자식 클래스인 ShoppingListItem// ShoppingListItem의 purchased은 false 상태로 시작classShoppingListItem:RecipeIngredient{varpurchased= false
vardescription:String{varoutput="\(quantity) x \(name)"
output += purchased ? " ✔":" ✘"return output
}}varbreakfastList=[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은 purchased에 초기 값을 지정하기 위한 이니셜라이저를 정의하지 않는다
쇼핑 리스트의 모든 물건은 구매하지 않은 상태로 시작하기 때문
새로운 ShoppingListItem 인스턴스를 만들기 위해 세 가지 상속된 이니셜라이저를 사용 가능
실패 가능한 이니셜라이저
실패할 수 있는 이니셜라이저를 사용하는 게 유용할 때가 있다
실패는 유효하지 않은 매개 변수, 요구되는 외부 자원의 부재, 또는 초기화 성공을 막는 다른 조건에 의해 발생
실패할 수 있는 초기화 조건에 대처하기 위해 하나 이상의 실패 가능한 이니셜라이저를 정의 가능
init 키워드 뒤에 물음표를 붙임으로써 실패 가능한 이니셜라이저를 작성(init?)
실패 가능한 이니셜라이저와 일반 이니셜라이저와 같은 매개 변수 타입과 이름을 갖도록 정의 불가
실패 가능한 이니셜라이저는 초기화한 타입의 옵셔널 값을 생성
실패 가능한 이니셜라이저 안에 return nil을 작성함으로써 초기화 실패가 발생할 수 있음을 표시
엄밀히 말하면 이니셜라이저는 값을 반환하지 않는다
self가 초기화가 끝나는 시점에 완전히 정확하게 초기화되었음을 보장하는 것이 이니셜라이저의 역할이며
초기화 실패를 유발하기 위해 return nil을 작성하긴 하지만
초기화 성공을 알리기 위해 return 키워드를 사용하지는 않는다
아래 예시에서, 숫자 타입 변환을 위한 실패 가능한 이니셜라이저가 구현된다.
숫자 타입 간의 변환이 값을 정확하게 유지함을 보장하기 위해,
init(exactly:) 이니셜라이저를 사용
타입 변환이 값을 유지하지 못하면 이니셜라이저는 실패
// Swift 기본 타입 Int에서 제공하는 실패 가능한 init(exactly:) 이니셜라이저letwholeNumber:Double= 12345.0
letpi= 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"letvalueChanged=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"
// 커스텀 클래스로 실패 가능한 이니셜라이저 구성// Animal 구조체를 정의// species 문자열 상수 프로퍼티를 가짐structAnimal{letspecies:String// species 매개 변수 하나를 갖는 실패 가능한 이니셜라이저를 정의// 이니셜라이저에 들어온 species 값이 비어 있는지 확인하고 만약 빈 문자열이 들어오면 초기화는 실패init?(species:String){
if species.isEmpty {returnnil}self.species = species
}}letsomeCreature=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"// 만약 빈 문자열이 들어온다면 초기화는 실패letanonymousCreature=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"
빈 문자열 값을 확인하는 것과 nil을 확인하는 것은 다른 측면의 문제
위 예시에서, 빈 문자열("")은 유효한 비옵셔널 문자열이다
하지만, species 프로퍼티의 값으로는 적절하지 않다
해당 제한 사항을 모델링하기 위해 빈 문자열이 발견되면 실패 가능한 이니셜라이저가 초기화 실패를 트리거
1) 열거형의 실패 가능한 이니셜라이저
열거형에도 실패 가능한 이니셜라이저를 사용할 수 있다
이니셜라이저는 매개 변수가 적절한 열거형 케이스와 매칭되지 않을 경우 초기화 실패 가능
// TemperatureUnit 열거형을 정의// 적절한 Character 열거형 케이스를 찾기 위해 실패 가능한 이니셜라이저를 사용// 적절한 열거형 케이스를 고르고, 만약 매개 변수가 이들 중 하나도 맞지 않을 경우,// 실패를 유발하기 위해 실패 가능한 이니셜라이저를 사용enumTemperatureUnit{case kelvin, celsius, fahrenheit
init?(symbol:Character){
switch symbol {case"K":self=.kelvin
case"C":self=.celsius
case"F":self=.fahrenheit
default:returnnil}}}letfahrenheitUnit=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."letunknownUnit=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."
2) Raw 값을 사용하는 열거형의 실패 가능한 이니셜라이저
raw 값을 사용하는 열거형은 자동으로 실패 가능한 이니셜라이저 init?(rawValue:)를 얻는다
해당 이니셜라이저는 적절한 raw 값 타입의 rawValue 매개 변수를 가지며,
매칭되는 열거형 케이스를 찾고 찾지 못할 경우 실패를 발생
// TemperatureUnit를 Character 타입의 raw 값을 사용하도록 수정 enumTemperatureUnit:Character{case kelvin ="K", celsius ="C", fahrenheit ="F"}letfahrenheitUnit=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."letunknownUnit=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."
3) 초기화 실패의 전파
클래스, 구조체, 열거형의 실패 가능한 이니셜라이저는
같은 클래스, 구조체, 열거형의 다른 실패 가능한 이니셜라이저에 위임할 수 있다
비슷하게, 자식 클래스의 실패 가능한 이니셜라이저는 부모 클래스의 실패 가능한 이니셜라이저에 위임할 수 있다
두 경우 모두 초기화 실패를 유발하는 다른 이니셜라이저에 위임할 경우 전체 초기화 과정은 즉시 실패
더 이상 초기화 코드가 실행되지 않는다
실패 가능한 이니셜라이저는 일반 이니셜라이저에 위임 가능
잠재적인 실패 상황을 실패가 없는 기존 초기화 과정에 더하고자 할 경우 사용
classProduct{letname:String// Product의 실패 가능한 이니셜라이저는 name이 빈 문자열인지 확인init?(name:String){
if name.isEmpty {returnnil}self.name = name
}}// Product의 자식 클래스인 CartItem을 정의// CartItem 클래스는 온라인 쇼핑 카트 속 아이템을 모델로 구현// 저장 상수 프로퍼티인 quantity 소유, 항상 적어도 1의 값을 갖고 있음을 보장classCartItem:Product{letquantity:Int// 실패 가능한 이니셜라이저는 quantity 값이 1 이상인지 확인init?(name:String, quantity:Int){// 만약 quantity가 유효하지 않다면 전체 초기화 과정은 즉시 실패, 더 이상의 코드 진행은 되지 않음
if quantity <1{returnnil}self.quantity = quantity
super.init(name: name)}}// 비어 있지 않은 문자열 name과 1 이상의 quantity로 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"
4) 실패 가능한 이니셜라이저 오버라이드 하기
자식 클래스에서 실패 가능한 이니셜라이저를 오버라이드 할 수 있다
부모 클래스의 실패 가능한 이니셜라이저를 자식 클래스의 일반 이니셜라이저로 오버라이드도 가능
이렇게 하면 부모 클래스의 초기화가 실패할 수 있어도, 초기화를 실패하지 않는 자식 클래스를 정의할 수 있다
부모 클래스의 실패 가능한 이니셜라이저를 자식 클래스의 일반 이니셜라이저로 오버라이드 한다면,
부모 클래스의 이니셜라이저로 위임하는 방법은 실패 가능한 부모 클래스 이니셜라이저의 결과를 강제로 푸는(force-unwrap) 것
실패 가능한 이니셜라이저를 일반 이니셜라이저로 오버라이드 할 수 있지만, 반대는 불가능
// Document 클래스를 정의// 옵셔널 String 타입의 name 프로퍼티 소유- 빈 문자열은 허용하지 않음classDocument{varname:String?// this initializer creates a document with a nil name valueinit(){}// this initializer creates a document with a nonempty name valueinit?(name:String){
if name.isEmpty {returnnil}self.name = name
}}// Document의 자식 클래스인 AutomaticallyNamedDocument를 정의// Document의 두 지정 이니셜라이저를 오버라이드// 오버라이드들은 AutomaticallyNamedDocument 인스턴스가 만약 이름이 없이 초기화될 경우// "[Untitled]"를 name 값으로 가지며, 빈 문자열은 init(name:) 이니셜라이저로 들어간다classAutomaticallyNamedDocument:Document{overrideinit(){
super.init()self.name ="[Untitled]"}// 부모 클래스의 init?(name:)을 일반 이니셜라이저인 init(name:)으로 오버라이드// 빈 문자열이 들어왔을 때 부모 클래스와 다르게 대처함으로써 이니셜라이저는 실패할 필요가 없어졌다 overrideinit(name:String){
super.init()
if name.isEmpty {self.name ="[Untitled]"}else{self.name = name
}}}// 자식 클래스의 이니셜라이저를 구현하면서 부모 클래스의 실패 가능한 이니셜라이저를 호출할 때 강제로 푸는 것이 가능// ex. UntitledDocument 자식 클래스의 name은 항상 [Untitled]이 된다.classUntitledDocument:Document{overrideinit(){
super.init(name:"[Untitled]")!
}}
만약 부모 클래스의 init(name:) 이니셜라이저가 name이 빈 문자열로 호출되면,
강제 언래핑 연산자가 런타임 에러를 발생
그러나, 문자열 상수로 호출되어 부모 쪽의 이니셜라이저가 실패하지 않으므로,
해당 경우에는 런타임 에러가 발생하지 않는다
일종의 옵셔널 바인딩에서의 가드와 유사
5) Init! 실패 가능한 이니셜라이저
암시적으로 풀린(implicitly unwrapped) 옵셔널 인스턴스를 생성하는 실패 가능한 이니셜라이저를 정의할 수 있다
init 키워드 뒤에 물음표 대신 느낌표를 붙임
init?에서 init!으로 위임하거나, init?을 init!으로 오버라이드 할 수 있다
반대의 경우도 가능
init에서 init!으로 위임할 수도 있다
init! 이니셜라이저는 초기화에 실패할 경우 assertion을 발생
요구된 이니셜라이저 (Required Initializers)
required 키워드를 클래스 이니셜라이저 선언 앞에 작성하여
모든 자식 클래스는 해당 이니셜라이저를 반드시 구현해야 함을 명시가 가능
자식 클래스에서 요구된 이니셜라이저를 구현할 때 반드시 required 키워드를 붙여야 한다
요구된 지정 이니셜라이저를 오버라이드 할 때는 override를 작성하지 않는다
classSomeSubclass:SomeClass{requiredinit(){// subclass implementation of the required initializer goes here}}
상속된 이니셜라이저가 요구 사항을 만족시킬 수 있다면
요구된 이니셜라이저를 명시적으로 구현할 필요는 없다
1) 클로저나 함수를 이용해 프로퍼티 기본 값 설정하기
저장 프로퍼티의 기본 값이 커스텀이나 셋업을 요구할 경우,
프로퍼티 기본 값에 클로저나 전역 함수를 이용 가능
해당 프로퍼티가 속한 타입의 새로운 인스턴스가 초기화될 때마다 클로저나 함수가 호출
그리고 반환 값이 프로퍼티 기본 값으로 할당
이런 종류의 클로저나 함수는 일반적으로 프로퍼티와 같은 타입의 임시 값을 반환
해당 임시 값은 프로퍼티 기본 값으로써 사용
classSomeClass{letsomeProperty:SomeType={// create a default value for someProperty inside this closure// someValue must be of the same type as SomeTypereturn someValue
}()}
클로저의 중괄호 끝에 빈 소괄호 쌍이 따라오는데
Swift가 클로저를 즉시 실행한다는 것 의미
만약, 소괄호를 생략한다면 클로저의 반환 값이 아니라 클로저 자신을 프로퍼티에 할당해야 한다
만약 프로퍼티를 초기화하는 데 클로저를 사용한다면,
클로저가 실행되는 시점에서 인스턴스의 나머지 프로퍼티 부분은 아직 초기화가 되지 않았다는 것을 기억해야 한다
따라서, 클로저 안에서 다른 프로퍼티에 접근할 수 없다
프로퍼티가 기본 값을 갖고 있더라도 사용할 수 없다
self 프로퍼티를 사용하거나 다른 메소드를 호출할 수도 없다
// Chessboard 구조체를 정의// 8 x 8 보드로 2차원 배열 정의, 검은색과 하얀색 정사각형으로 구분// . true는 검은색 사각형을, false는 하얀색 사각형을 표시structChessboard{// 길이가 64인 Bool 배열 boardColors 프로퍼티를 소유// boardColors 배열은 클로저를 사용해 초기화// 배열의 첫 번째 아이템은 좌상단 모서리이며, 마지막 아이템은 우하단 모서리letboardColors:[Bool]={vartemporaryBoard=[Bool]()varisBlack= 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{returnboardColors[(row *8)+ column]}}letboard=Chessboard()print(board.squareIsBlackAt(row:0, column:1))// Prints "true"print(board.squareIsBlackAt(row:7, column:7))// Prints "false"
새로운 Chessboard 인스턴스가 만들어질 때마다 클로저가 실행되고, boardColors의 기본 값이 계산되어 반환
The text was updated successfully, but these errors were encountered:
초기화(Initialization)은 클래스, 구조체, 열거형의 인스턴스를 사용하기 위해 준비하는 과정
각각의 저장 프로퍼티에 초기 값을 설정하고,
새로운 인스턴스의 사용이 준비되기 전에 필요한 다른 세팅이나 초기화 작업을 실행
이니셜라이저(initializer)를 정의해서 초기화 과정을 구현
특정 타입의 새로운 인스턴스를 생성하기 위해 호출할 수 있는 특별한 메소드와 흡사
Objective-C의 이니셜라이저와 달리 Swift의 이니셜라이저는 값을 반환하지 않는다
이니셜라이저의 가장 큰 역할은 타입의 새로운 인스턴스가 처음으로 사용되기 전에 정확히 초기화됐는지를 보장
클래스 타입의 인스턴스는 디이니셜라이저(deinitializer)를 구현할 수도 있다
클래스의 인스턴스가 할당을 해제하기 전에 메모리 정리를 하는 것
저장 프로퍼티에 초기 값 설정하기
클래스와 구조체는 인스턴스가 생성되는 시점에
반드시 모든 저장 프로퍼티에 적당한 초기값을 설정
저장 프로퍼티는 미결정된 상태로 남겨질 수 없다
이니셜라이저를 통해 저장 프로퍼티에 값을 설정하거나
프로퍼티 정의의 일부분으로써 프로퍼티 기본 값을 할당 가능
저장 프로퍼티에 기본 값을 할당하거나 이니셜라이저로 초기 값을 설정할 때,
프로퍼티의 값은 프로퍼티 옵저버를 호출하지 않고 직접 설정
1) 이니셜라이저 (Initializer)
이니셜라이저(initializer)는 특정 타입의 새로운 인스턴스를 생성
가장 간단한 형태의 이니셜라이저는 init 키워드를 매개 변수가 없는 인스턴스 메소드처럼 작성
2) 프로퍼티 기본 값
이니셜라이저에서 저장 프로퍼티에 초기 값을 설정하는 대신
프로퍼티 선언에서 기본 값을 할당 가능
프로퍼티가 정의될 때 초기 값을 기본 프로퍼티 값으로써 할당
만약 프로퍼티가 항상 같은 초기 값을 가진다면,
기본 값을 제공하는 것이 이니셜라이저를 사용하는 것보다 낫다
결과는 동일하지만, 기본 값을 제공하는 건 이니셜라이저 내부의 할당보다 프로퍼티의 초기화에 더 밀접하게 연결되어 있기 때문
더 짧고 명확하며, 기본 값으로부터 프로퍼티의 타입을 추론할 수 있게 해 준다
기본 값은 또한 기본 이니셜라이저와 이니셜라이저 상속의 이점을 더 쉽게 가져 갈 수 있게 만든다
커스터마이징 초기화
입력 매개 변수와 옵셔널 프로퍼티 타입을 사용하거나,
초기화 중에 상수 프로퍼티를 할당하여 초기화 과정을 커스터마이징 가능하다
1) 초기화 매개 변수
초기화 정의의 일부분으로써 초기화 과정을 커스터마이징 하는 값의 이름과 타입을 정의하기 위해
초기화 매개 변수(initialization parameter)를 제공 가능
초기화 매개 변수는 함수 / 메서드의 매개 변수 문법과 같은 기능을 가진다
두 이니셜라이저는 하나의 인자를 일치하는 Celsius 값으로 변환하고,
temperatureInCelsius 프로퍼티에 값을 저장
2) 매개 변수 이름과 인수 라벨
함수와 메서드 매개 변수처럼
초기화 매개 변수도 이니셜라이저의 바디에서 사용하는 매개 변수와
이니셜라이저를 호출할 때 사용하는 인수 라벨을 가질 수 있다
하지만, 이니셜라이저는 함수나 메서드처럼 식별 함수 이름이 없다
이니셜라이저 매개 변수의 이름과 타입은 어떤 이니셜라이저가 호출되는지 식별할 때 중요한 역할
때문에 Swift는 인수 라벨을 붙이지 않은 모든 매개 변수에 자동으로 인수 라벨을 제공
3) 인수 라벨이 없는 이니셜라이저 매개 변수
만약 이니셜라이저 매개 변수에 인수 라벨을 사용하기 싫다면
언더스코어(_)를 인수 라벨 대신 사용 가능
4) 옵셔널 프로퍼티 타입
만약 커스텀 타입이 초기화 과정 중 할당되지 않을 수도 있는 값이라, 혹은 나중에 값이 할당되기 때문에
nil 상태를 허용한다면, 프로퍼티를 옵셔널 타입으로 선언
옵셔널 타입의 프로퍼티는 자동적으로 nil 값으로 초기화
5) 초기화 중에 상수 프로퍼티를 할당하기
초기화할 때 상수 프로퍼티에도 값을 할당 가능
한번 상수 프로퍼티에 값을 할당하면, 더 이상 수정 불가
클래스 인스턴스의 경우 상수 프로퍼티는 해당 프로퍼티를 도입한 클래스에 의해서 초기화 중에만 수정 가능
자식 클래스에서는 수정 불가
기본 이니셜라이저 (Default Initializer)
구조체나 클래스의 모든 프로퍼티가 기본 값을 갖고 있는 상황에서 이니셜라이저를 정의하지 않는다면,
Swift는 기본 이니셜라이저(default initializer)를 제공
기본 이니셜라이저는 모든 프로퍼티를 기본 값으로 초기화
모든 프로퍼티가 기본 값을 갖고 있으면서 부모 클래스가 없는 기반 클래스이기 때문에,
ShoppingListItem는 자동적으로 기본 이니셜라이저를 얻게 된다
name 프로퍼티는 옵셔널 문자열 프로퍼티여서 기본 값을 작성하지 않는 경우 nil
ShoppingListItem()과 같이 새로운 인스턴스를 생성할 때 기본 이니셜라이저가 사용
1) 구조체 타입을 위한 멤버 이니셜라이저
만약 구조체가 다른 커스텀 이니셜라이저를 정의하지 않는다면,
멤버 이니셜라이저(memberwise initializer)를 자동으로 얻는다
클래스와는 달리 구조체는 저장 프로퍼티가 기본 값을 갖지 않아도 멤버 이니셜라이저를 얻는 게 가능
멤버 이니셜라이저는 새로운 구조체 인스턴스의 멤버 프로퍼티를 초기화하는 방법을 축약한 것
새로운 인스턴스의 프로퍼티가 갖는 초기 값은 이름에 의해 멤버 이니셜라이저에 넣어질 수 있다
값 타입을 위한 이니셜라이저 위임
이니셜라이저는 인스턴스 초기화의 일부분을 수행하기 위해
다른 이니셜라이저를 호출 가능
이니셜라이저 위임(initializer delegation)이라 알려진 과정은 다수의 이니셜라이저 간에 코드의 중복을 막을 수 있다
이니셜라이저 위임이 작동하는 방식에 대한 규칙과 어떤 위임 형태가 혀용되는지는 값 타입과 클래스 타입이 다르다
값 타입(구조체와 열거형)은 상속을 지원하지 않고,
자체적으로 제공하는 다른 이니셜라이저에만 위임이 가능하다
따라서 이니셜라이저 위임 과정은 상대적으로 간단
반면 클래스는 다른 클래스로부터 상속을 받을 수 있다
이는 클래스는 추가적인 책임을 갖고 있다는 것을 뜻한다
상속한 모든 저장 프로퍼티가 초기화 과정에서 적당한 값을 할당받았는지 보장해야 한다
값 타입은 커스텀 이니셜라이저를 작성할 때 self.init을 사용하여
같은 값 타입으로부터의 다른 이니셜라이저를 참조 가능
이니셜라이저 안에서만 self.init을 호출할 수 있다
만약 값 타입을 위한 커스텀 이니셜라이저를 정의한다면,
그 타입의 기본 이니셜라이저(또는 구조체의 경우, 멤버 이니셜라이저)에 더 이상 접근이 불가하며
이러한 제약은 더 복잡한 이니셜라이저에 의해 제공된 필수적인 추가 셋업이
자동으로 생성된 이니셜라이저를 사용하는 무언가에 의해 순환참조되는 것을 막는다
init(center:size:) 이니셜라이저는 origin과 size의 새로운 값을 적당한 프로퍼티에 직접 할당할 수도 있지만
기존 이니셜라이저를 사용하여 위임하는게 더 편하고 명확히 의도를 드러낼 수 있다
init() 및 init(origin:size:) 이니셜라이저를 직접 정의하지 않고 예제의 구조체를 extension하여 구성할수도 있다
클래스 상속과 초기화
모든 클래스의 저장 프로퍼티는 반드시 초기화 과정에서 초기 값을 할당 받아야 한다
부모 클래스에서 상속 받은 프로퍼티도 마찬가지
Swift는 클래스 타입을 위한 두 종류의 이니셜라이저를 정의하며
모든 저장 프로퍼티가 초기 값을 받는 걸 보장하는 데 도움을 준다
1) 지정 이니셜라이저와 편리한 이니셜라이저 (Designed Initializers and Convenience Initializers)
지정 이니셜라이저(designed initializer)는 클래스의 메인 이니셜라이저
지정 이니셜라이저는 클래스의 모든 프로퍼티를 완전히 초기화
그리고 부모 클래스의 이니셜라이저를 호출하여 부모 클래스 연쇄를 통한 초기화 과정을 계속 진행
클래스는 최소한의 지정 이니셜라이저를 갖도록 의도된다
일반적으로 하나의 지정 이니셜라이저를 갖으며
지정 이니셜라이저는 초기화가 발생하고 부모 클래스 체인으로 이어지는 "깔대기" 포인트와 같다
모든 클래스는 적어도 하나의 지정 이니셜라이저를 가져야 한다
부모 클래스로부터 하나 이상의 지정 이니셜라이저를 상속받아야 하는 경우도 있다
편리한 이니셜라이저(convenience initializer)는 클래스의 이니셜라이저를 지원하는 서브 이니셜라이저다
편리한 이니셜라이저를 정의하여 편리한 이니셜라이저와 동일한 클래스의 지정 이니셜라이저를 호출 가능하며
편리한 이니셜라이저로 지정 이니셜라이저의 매개변수에 기본 값을 설정한다
또한 편리한 이니셜라이저를 정의하여 특정 사용 사례 또는 입력 값 타입에 대해 해당 클래스의 인스턴스를 만드는 것도 가능
만약 클래스에서 필요하지 않다면 편리한 이니셜라이저를 만들 필요가 없다
공통적인 초기화 패턴에 대한 축약이 시간을 절약하거나
클래스 초기화의 의도를 더 명확하게 만들 때마다 편리한 이니셜라이저를 만들면 된다
2) 지정 이니셜라이저와 편리한 이니셜라이저 문법
지정 이니셜라이저는 값 타입의 이니셜라이저와 같은 방식으로 쓰여진다
편리한 이니셜라이저는 같은 스타일이지만, convenience 키워드를 init 앞에 붙인다
3) 클래스 타입을 위한 이니셜라이저 위임
지정 이니셜라이저와 편리한 이니셜라이저의 관계를 간단히 하기 위해,
Swift는 이니셜라이저 간 위임 호출이 일어날 때 다음 세 가지 규칙을 적용
간단히 말하자면 이렇다.
이미지로 표현하면 다음과 같다.
여기서 부모클래스는 하나의 지정 이니셜라이저와 두 개의 편리한 이니셜라이저를 가지고 있다
하나의 편리한 이니셜라이저는 다른 편리한 이니셜라이저를 호출하고, 이는 다시 단일 지정 이니셜라이저를 호출
이 건 규칙 2와 3을 만족한다
부모클래스 자체에는 더 이상의 슈퍼클래스가 없으므로 규칙 1은 적용되지 않는다
그림의 자식 클래스에는 두 개의 지정 이니셜라이저와 한 개의 편리한 이니셜라이저가 있다
편리한 이니셜라이저는 동일한 클래스의 다른 이니셜라이저만 호출할 수 있으므로
두 개의 지정 이니셜라이저 중 하나를 호출해야 한다
이 건 위에서부터 규칙 2와 3을 만족한다
두 지정 이니셜라이저 모두 부모클래스의 단일 지정 이니셜라이저를 호출하여 위의 규칙 1을 충족하게 된다
해당 규칙은 클래스의 사용자가 각 클래스의 인스턴스를 만드는 방법에는 아무 영향을 끼치지 않는다
그림에서 어떤 이니셜라이저를 사용해도 인스턴스를 구성 가능하다
클래스 이니셜라이저의 구현을 어떻게 작성할 것인지에만 영향을 끼친다
위의 그림은 4개 클래스에 대한 더 복잡한 클래스 계층을 보여준다
계층에서 지정 이니셜라이저가 클래스 초기화를 위한 "깔대기" 포인트로 작용하여
체인의 클래스 간의 상호 관계를 단순화하는 방법을 나타낸다
4) 2단계 초기화 (Two-Phase Initialization)
Swift에서 클래스 초기화는 2단계로 진행된다
첫 번째 단계는 각 저장 프로퍼티는 클래스에서 정한 초기값으로 초기화
모든 저장 프로퍼티의 상태가 결정되면 두 번째 단계가 시작
두 번째 단계는 새로운 인스턴스의 사용이 준비됐다고 알려주기 전에 저장 프로퍼티를 커스터마이징하는 단계
2단계 초기화의 사용은 클래스 상속에서 각 클래스에 완전한 융통성을 주면서도 초기화를 안전하게 만들어 준다
2단계 초기화는 초기화되기 전에 값에 접근되는 것을 방지
예상치 못한 다른 이니셜라이저에 의해 다른 값이 프로퍼티 값에 설정되는 것을 예방
Swift의 컴파일러는 2단계 초기화가 에러없이 끝나는 것을 보장하기 위해 4단계 안전 확인(safety-check)을 한다
안전 확인 1단계
지정 이니셜라이저는 클래스의 모든 프로퍼티가 부모 클래스의 이니셜라이저에 위임하기 전에 초기화됐음을 보장해야 한다. 객체의 메모리는 모든 저장 프로퍼티의 초기 상태가 확인돼야만 완전히 초기화된 것으로 간주한다. 규칙이 충족되려면 지정 이니셜라이저가 체인을 넘겨주기 전에 자신의 모든 프로퍼티가 초기화되었는지 확인해야 한다.
안전 확인 2단계
지정 이니셜라이저는 반드시 상속한 프로퍼티에 값을 할당하기 전에 부모 클래스의 이니셜라이저로 위임해야 한다. 그렇지 않으면 지정 이니셜라이저가 할당한 새로운 값은 부모 클래스에 의해 덮어 쓰여진다.
안전 확인 3단계
편리한 이니셜라이저는 어떤 프로퍼티에 값을 할당하기 전에 반드시 다른 이니셜라이저에 위임해야 한다. 그렇지 않으면 편리한 이니셜라이저가 할당한 새로운 값은 클래스의 지정 이니셜라이저에 의해 덮어 쓰여진다.
안전 확인 4단계
이니셜라이저는 초기화의 1단계가 끝나기 전까지 다른 인스턴스 메소드를 호출하거나, 인스턴스 프로퍼티의 값을 읽거나, 값으로써 self를 참조할 수 없다.
클래스 인스턴스는 1단계가 끝나기 전까지는 완전히 유효하지 않다
1단계 종료 시점부터 프로퍼티에 접근하거나, 메소드를 호출할 수 있고, 클래스 인스턴스가 유효해진다
4단계 안전 확인에 기반하여 2단계 초기화는 다음과 같이 작동한다.
1단계
2단계
2단계 초기화를 도식화하면 다음과 같다
1단계에서 자식 클래스 및 부모 클래스에 대한 초기화 호출을 찾는 방법
초기화는 자식 클래스의 편리한 이니셜라이저에 대한 호출로 시작
편리한 이니셜라이저는 아직 프로퍼티를 수정할 수 없다
해당 작업은 같은 클래스의 지정 이니셜라이저에게 위임
지정 이니셜라이저는 안전 확인 1에 따라 자식 클래스의 모든 프로퍼티에 값이 있는지 확인
그런 다음 부모 클래스의 지정 이니셜라이저를 호출하여 체인을 따라 초기화를 계속 진행
부모 클래스의 지정 이니셜라이저는 모든 부모 클래스 프로퍼티에 값이 있는지 확인
더 이상 초기화할 부모 클래스가 없으므로(기반 클래스) 더 이상의 위임이 필요하지 않다
부모 클래스의 모든 프로퍼티가 초기값을 가지는 즉시 메모리가 완전히 초기화된 것으로 간주되며 1단계는 완료
2단계에서 동일한 초기화 호출을 찾는 방법
1단계 완료 후, 부모 클래스의 지정 이니셜라이저는 이제 인스턴스를 추가적인 커스텀 작업을 수행 가능
부모 클래스의 지정 이니셜라이저가 완료되면 자식 클래스의 지정 이니셜라이저가 추가적인 커스텀 작업을 수행 가능
(추가적인 커스텀은 선택적인 사항이기에 반드시 추가적으로 수행할 필요는 없음)
마지막으로 자식 클래스의 지정 이니셜라이저가 완료되면 원래 처음 호출된 편의 이니셜라이저가 추가 커스터마이징을 수행 가능
5) 이니셜라이저 상속과 오버라이딩
Swift의 자식 클래스는 Objective-C의 자식 클래스와는 달리, 부모 클래스의 이니셜라이저를 기본 값으로 상속하지 않는다
Swift의 접근 방식은 부모 클래스의 간단한 이니셜라이저가 보다 복잡한 자식 클래스에 상속되어
완전히 또는 올바르게 초기화되지 않은 자식 클래스의 새 인스턴스를 생성하는 상황을 방지한다
만약 부모 클래스와 동일한 이니셜라이저를 자식 클래스에서 만들고자 한다면,
자식 클래스에서 이런 이니셜라이저의 커스텀 작업을 구현하면 된다
부모 클래스의 지정 이니셜라이저와 매치되는 자식 클래스 이니셜라이저를 만들 때,
지정 이니셜라이저를 오버라이드 가능
따라서, 자식 클래스의 이니셜라이저를 정의할 때 override 키워드를 붙여야 한다
자동으로 제공된 기본 이니셜라이저를 오버라이딩 할 때도 마찬가지
오버라이드된 프로퍼티, 메소드, 서브스크립트처럼 override 키워드의 존재는
Swift에게 부모 클래스가 매치되는 지정 이니셜라이저를 갖고 있는지,
오버라이딩 하는 이니셜라이저의 매개 변수는 유효한지 확인하게 한다
거꾸로, 만약 부모 클래스의 편리한 이니셜라이저와 매치되는 자식 클래스의 이니셜라이저를 작성한다면,
부모 클래스의 편리한 이니셜라이저는 위에서 언급한 이니셜라이저 위임 규칙 때문에
자식 클래스에 의해 절대 직접적으로 호출되지 않는다.
그러므로, 자식 클래스는 부모 클래스의 편리한 이니셜라이저를 오버라이딩 할 수 없다
결과적으로, 부모 클래스의 편리한 이니셜라이저와 매치되는 이니셜라이저를 구현할 때는 override 키워드를 쓰면 안 된다
이 기반 클래스는 기본 값이 0인 numberOfWheels 저장 프로퍼티를 선언한다. numberOfWheels 프로퍼티는 계산 프로피티인 description에서 차량의 특성을 묘사하기 위해 사용된다.
만약 자식 클래스의 이니셜라이저가 2번째 초기화 단계에서 아무런 커스터마이징도 하지 않고,
부모 클래스가 인자 없는 이니셜라이저를 갖고 있다면, super.init()을 생략 가능
6) 자동 이니셜라이저 상속
자식 클래스는 부모 클래스의 이니셜라이저를 기본으로 상속하지는 않는다
하지만, 특정한 조건을 충족한다면 부모 클래스의 이니셜라이저가 자동으로 상속
사실 많은 상황에서 직접 이니셜라이저를 오버라이드 할 필요는 없으며
안전할 때만 최소한의 노력으로 부모 클래스 이니셜라이저를 상속 가능
자식 클래스에서 추가한 모든 프로퍼티에 기본 값을 제공하면 다음 두 가지 규칙이 적용된다
만약 자식 클래스가 지정 이니셜라이저를 정의하지 않았다면, 자동으로 부모 클래스의 모든 지정 이니셜라이저를 상속한다.
자식 클래스가 규칙 1에 따라 상속하거나 정의의 일부로 커스텀을 통하여 모든 부모 클래스 지정 이니셜라이저를 구현하는 경우
모든 부모 클래스 편의 이니셜라이저를 자동으로 상속한다.
이러한 규칙은 자식 클래스가 편리한 이니셜라이저를 추가하더라도 적용된다.
7) 지정 / 편리한 이니셜라이저의 사용
다음 예제에서는 지정 이니셜라이저, 편리한 이니셜라이저 및 자동으로 이니셜라이저 상속이 수행되고 있음을 보여준다
Food, RecipeIngredient, ShoppingListItem 클래스의 계층과 이니셜라이저가 상호작용하는 방식을 정의
새로운 Food 인스턴스의 모든 저장 프로퍼티가 완전히 초기화되는 것을 보장하기 때문에
init(name: String) 이니셜라이저는 지정 이니셜라이저가 된다
Food 클래스는 부모 클래스가 없기 때문에 super.init()을 호출할 필요가 없다
Food 클래스는 인자가 없는 편리한 이니셜라이저인 init()을 제공한다
init() 이니셜라이저는 init(name: String)에 [Unnamed]를 name 값으로 하여 위임
RecipeIngredient은 하나의 지정 이니셜라이저인 init(name: String, quantity: Int)을 갖는다
모든 프로퍼티를 채우는 데 사용
해당 이니셜라이저는 quantity 인자를 quantity 프로퍼티에 할당
그런 뒤에 Food 클래스의 init(name: String)에 이니셜라이저를 위임
해당 과정은 2단계 초기화의 안전 확인 1단계를 만족
RecipeIngredient은 편리한 이니셜라이저인 init(name: String)도 정의한다
해당 편리한 이니셜라이저는 명시적 quantity가 없이
생성된 모든 RecipeIngredient의 인스턴스는 quantity가 1이라고 가정
편리한 이니셜라이저는 RecipeIngredient의 인스턴스를 더 빠르고 편리하게 만들 수 있게 구성
quantity가 1인 RecipeIngredient 인스턴스를 여러 개 만들 때 코드의 중복을 예방 가능
init(name: String) 편리한 이니셜라이저는 Food의 init(name: String) 지정 이니셜라이저와 같은 매개 변수를 취한다
해당 편리한 이니셜라이저가 부모 클래스의 지정 이니셜라이저를 오버라이드 하기 때문에
override 키워드를 표시
RecipeIngredient의 init(name: String)가 편리한 이니셜라이저이긴 하지만,
RecipeIngredient는 부모 클래스의 모든 이니셜라이저를 구현하고 있다
따라서, RecipeIngredient는 부모 클래스의 편리한 이니셜라이저 역시 자동으로 상속 가능
또한, Food의 편리한 이니셜라이저인 init() 역시 사용 가능
새로운 ShoppingListItem 인스턴스를 만들기 위해 세 가지 상속된 이니셜라이저를 사용 가능
실패 가능한 이니셜라이저
실패할 수 있는 이니셜라이저를 사용하는 게 유용할 때가 있다
실패는 유효하지 않은 매개 변수, 요구되는 외부 자원의 부재, 또는 초기화 성공을 막는 다른 조건에 의해 발생
실패할 수 있는 초기화 조건에 대처하기 위해 하나 이상의 실패 가능한 이니셜라이저를 정의 가능
init 키워드 뒤에 물음표를 붙임으로써 실패 가능한 이니셜라이저를 작성(init?)
실패 가능한 이니셜라이저는 초기화한 타입의 옵셔널 값을 생성
실패 가능한 이니셜라이저 안에 return nil을 작성함으로써 초기화 실패가 발생할 수 있음을 표시
아래 예시에서, 숫자 타입 변환을 위한 실패 가능한 이니셜라이저가 구현된다.
숫자 타입 간의 변환이 값을 정확하게 유지함을 보장하기 위해,
init(exactly:) 이니셜라이저를 사용
타입 변환이 값을 유지하지 못하면 이니셜라이저는 실패
1) 열거형의 실패 가능한 이니셜라이저
열거형에도 실패 가능한 이니셜라이저를 사용할 수 있다
이니셜라이저는 매개 변수가 적절한 열거형 케이스와 매칭되지 않을 경우 초기화 실패 가능
2) Raw 값을 사용하는 열거형의 실패 가능한 이니셜라이저
raw 값을 사용하는 열거형은 자동으로 실패 가능한 이니셜라이저 init?(rawValue:)를 얻는다
해당 이니셜라이저는 적절한 raw 값 타입의 rawValue 매개 변수를 가지며,
매칭되는 열거형 케이스를 찾고 찾지 못할 경우 실패를 발생
3) 초기화 실패의 전파
클래스, 구조체, 열거형의 실패 가능한 이니셜라이저는
같은 클래스, 구조체, 열거형의 다른 실패 가능한 이니셜라이저에 위임할 수 있다
비슷하게, 자식 클래스의 실패 가능한 이니셜라이저는 부모 클래스의 실패 가능한 이니셜라이저에 위임할 수 있다
두 경우 모두 초기화 실패를 유발하는 다른 이니셜라이저에 위임할 경우 전체 초기화 과정은 즉시 실패
더 이상 초기화 코드가 실행되지 않는다
실패 가능한 이니셜라이저는 일반 이니셜라이저에 위임 가능
잠재적인 실패 상황을 실패가 없는 기존 초기화 과정에 더하고자 할 경우 사용
4) 실패 가능한 이니셜라이저 오버라이드 하기
자식 클래스에서 실패 가능한 이니셜라이저를 오버라이드 할 수 있다
부모 클래스의 실패 가능한 이니셜라이저를 자식 클래스의 일반 이니셜라이저로 오버라이드도 가능
이렇게 하면 부모 클래스의 초기화가 실패할 수 있어도, 초기화를 실패하지 않는 자식 클래스를 정의할 수 있다
부모 클래스의 실패 가능한 이니셜라이저를 자식 클래스의 일반 이니셜라이저로 오버라이드 한다면,
부모 클래스의 이니셜라이저로 위임하는 방법은 실패 가능한 부모 클래스 이니셜라이저의 결과를 강제로 푸는(force-unwrap) 것
만약 부모 클래스의 init(name:) 이니셜라이저가 name이 빈 문자열로 호출되면,
강제 언래핑 연산자가 런타임 에러를 발생
그러나, 문자열 상수로 호출되어 부모 쪽의 이니셜라이저가 실패하지 않으므로,
해당 경우에는 런타임 에러가 발생하지 않는다
일종의 옵셔널 바인딩에서의 가드와 유사
5) Init! 실패 가능한 이니셜라이저
암시적으로 풀린(implicitly unwrapped) 옵셔널 인스턴스를 생성하는 실패 가능한 이니셜라이저를 정의할 수 있다
init 키워드 뒤에 물음표 대신 느낌표를 붙임
init?에서 init!으로 위임하거나, init?을 init!으로 오버라이드 할 수 있다
반대의 경우도 가능
init에서 init!으로 위임할 수도 있다
init! 이니셜라이저는 초기화에 실패할 경우 assertion을 발생
요구된 이니셜라이저 (Required Initializers)
required 키워드를 클래스 이니셜라이저 선언 앞에 작성하여
모든 자식 클래스는 해당 이니셜라이저를 반드시 구현해야 함을 명시가 가능
자식 클래스에서 요구된 이니셜라이저를 구현할 때 반드시 required 키워드를 붙여야 한다
요구된 지정 이니셜라이저를 오버라이드 할 때는 override를 작성하지 않는다
1) 클로저나 함수를 이용해 프로퍼티 기본 값 설정하기
저장 프로퍼티의 기본 값이 커스텀이나 셋업을 요구할 경우,
프로퍼티 기본 값에 클로저나 전역 함수를 이용 가능
해당 프로퍼티가 속한 타입의 새로운 인스턴스가 초기화될 때마다 클로저나 함수가 호출
그리고 반환 값이 프로퍼티 기본 값으로 할당
이런 종류의 클로저나 함수는 일반적으로 프로퍼티와 같은 타입의 임시 값을 반환
해당 임시 값은 프로퍼티 기본 값으로써 사용
클로저의 중괄호 끝에 빈 소괄호 쌍이 따라오는데
Swift가 클로저를 즉시 실행한다는 것 의미
만약, 소괄호를 생략한다면 클로저의 반환 값이 아니라 클로저 자신을 프로퍼티에 할당해야 한다
만약 프로퍼티를 초기화하는 데 클로저를 사용한다면,
클로저가 실행되는 시점에서 인스턴스의 나머지 프로퍼티 부분은 아직 초기화가 되지 않았다는 것을 기억해야 한다
따라서, 클로저 안에서 다른 프로퍼티에 접근할 수 없다
프로퍼티가 기본 값을 갖고 있더라도 사용할 수 없다
self 프로퍼티를 사용하거나 다른 메소드를 호출할 수도 없다
새로운 Chessboard 인스턴스가 만들어질 때마다 클로저가 실행되고, boardColors의 기본 값이 계산되어 반환
The text was updated successfully, but these errors were encountered: