/**
실패가능 생성자
- 실패가능 생성자는 인스턴스 생성에 실패할 수도 있는 가능성을 가진 생성자 (클래스, 구조체, 열거형 가능)
( 실패가 불가능하게 만들어서, 아예 에러가 나고 앱이 완전히 꺼지는 가능성보다는
실패가능 가능성 생성자를 정의하고 그에 맞는 예외 처리를 하는 것이 더 올바른 방법임)
- 생성자에 ?를 붙여서 init(파라미터) 라고 정의하면 실패가능 생성자로 정의되는 것임
(다만, 동일한 파라미터를 가진 생성자는 유일해야함. 오버로딩으로 실패가능 / 불가능을 구분 짖지 못함)
*/
struct Animal {
let species: String
// 실패가능 생성자
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
let a = Animal(species: "Giraffe")
let b = Animal(species: "")
// 엄밀히 말하면 생성자는 값을 반환하지 않고, 초기화가 끝날 때까지 모든 저장 속성이 값을 가져 올바르게 초기화되도록하는 것임
// 초기화 성공을 나타내기 위해 return 키워드를 사용하지 않음 (문법적 약속)
/**
열거형의 실패가능 생성자 활용
*/
enum TemperatureUnit {
case kelvin
case celsius
case fahrenheit
// 실패가능 생성자
init?(symbol: Character) {
switch symbol {
case "K":
self = .kelvin
case "C":
self = .celsius
case "F":
self = .fahrenheit
default:
return nil
}
}
}
let c: TemperatureUnit = TemperatureUnit.celsius // 옵셔널타입이 아님
let f: TemperatureUnit? = TemperatureUnit(symbol: "F") // 실패가능하기 때문에 옵셔널 타입임.
let x: TemperatureUnit? = TemperatureUnit(symbol: "X")
enum temperatureUnit1: Character {
case kelvin = "K"
case celsius = "C"
case fahrenheit = "F"
}
let f1: temperatureUnit1? = temperatureUnit1(rawValue: "F")
let c1: temperatureUnit1? = temperatureUnit1(rawValue: "C")
// 1. 초기화 실패의 전달 (호출관계)
/**
- (한마디로) 실패불가능 생성자는 다른 실패가능 생성자를 호출 불가능 🤙🏻
(동일단계 - 델리게이트 어크로스)
- 실패가능 ===> 실패불가능 (호출/위임) (OK)
- 실패불가능 ===> 실패가능 (호출/위임) (X)
(상속관계 - 델리게이트 업)
- (상위) 실패가능 <=== (하위)실패불가능 (호출/위임) (X)
- (상위) 실패불가능 <=== (하위)실패가능 (호출/위임) (OK)
- 두 경우 모두 초기화 실패를 유발하는 다른 생성자에 위임하면
- 전체 초기화 프로세스가 즉시 실패하고 더 이상 초기화 코드가 실행되지 않음
*/
// 예제 1
struct Item {
var name = ""
// 실패불가능 생성자
// init() {
//
// }
init() {
// self.init(name: "헬로") // 실패불가능은 실패가능을 호출할 수 없다.
}
// 실패가능 생성자
init?(name: String) {
self.init() // 실패가능은 실패불가능 호출을 할 수 있다.
}
}
// 상속관계에서의 호출 예제
// 상품
class Product {
let name: String
init?(name: String) {
if name.isEmpty {return nil}
self.name = name
}
}
// 온라인 쇼핑 카트의 항목을 모델링
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 {return nil}
self.quantity = quantity
super.init(name: name)
}
}
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("아이탬: \(twoSocks.name), 수량: \(twoSocks.quantity)")
}
if let zeroShirts = CartItem(name: "shirt", quantity: 0 ) {
print("아이템: \(zeroShirts.name), 수량: \(zeroShirts.quantity)")
} else {
print("zero shirts를 초기화 불가(갯수가 없음)")
}
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("아이템: \(oneUnnamed.name), 수량: \(oneUnnamed.quantity)")
} else {
print("이름없는 상품 초기화 불가")
}
// 2) (상속관계에서) 재정의 하기
/**
- (상위) 실패가능 ===> (하위) 실패불가능 재정의 (OK) (강제 언래핑 활용 가능)
- (상위) 실패불가능 ===> (하위) 실패가능 재정의 (X)
*/
// 범위로 따져보기(실패가능 생성자의 범위가 더 큼)
// 서류라는 클래스 정의
class Document {
var name: String?
init() {}
init?(name: String) {
if name.isEmpty { return nil}
self.name = name
}
}
// 자동으로 이름지어지는 서류
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
}
}
}
let autoDoc = AutomaticallyNamedDocument(name: "")
autoDoc.name
// 이름없는(Untitled) 서류
class UntitledDocument: Document {
override init() { // 재정의 (상위) 실패가능 ====> (하위) 실패불가능
super.init(name: "[Untitled]")! // 강제 언래핑(!)으로 구현 🤙🏻
}
}
/**
실패가능 생성자를 init!(파라미터)로 정의하기
*/