大器晩成
실패가능 생성자(Failable Initializers) 본문
실패가능 생성자(Failable Initializers) - init?(파라미터)
- 실패가능 생성자는 인스턴스 생성에 실패할 수도 있는 가능성을 가진 생성자입니다. (클래스, 구조체, 열거형 가능)
- 실패가 불가능하게 만들어서, 아예 에러가 나고 앱이 완전히 꺼지는 가능성보다는 실패가능 가능성 생성자를 정의하고 그에 맞는 예외 처리를 하는 것이 더 올바른 방법입니다.
- 생성자에 ?를 붙여서 init?(파라미터)라고 정의하면 실패가능 생성자로 정의되는 것입니다.
- (다만, 동일한 파라미터를 가진 생성자는 유일해야 합니다. 오버로딩으로 실패가능/불가능을 구분 짓지 못함)
- 실패가능 생성자는 옵셔널 타입입니다.
struct Animal {
let species: String
// 실패가능 생성자
init?(species: String) {
if species.isEmpty { // isEmpty == "" 빈문자열
return nil // 생성자 내에서 실패 가능 부분에 nil을 리턴하면 됨 (문법적 약속)
}
self.species = species
}
// 오버로딩 불가능 (동일한 파라미터를 가진 생성자는 유일해야함)
// init(species: String) {
//
// }
}
let someCreature = Animal(species: "Giraffe") // ====> 인스턴스 생성
if let giraffe = someCreature {
print("초기화된 동물의 종은 \(giraffe.species)")
}
let anonymousCreature = Animal(species: "") // 문자열이기에 유효한 타입이지만 =====> nil
if anonymousCreature == nil {
print("익명 크리쳐는 초기활 수 없습니다. ")
}
- 엄밀히 말하면 초기화 구문은 값을 반환하지 않습니다. 오히려 초기화 구문의 역할은 모든 저장속성이 값을 가져 완전하고 정확하게 초기화되도록 하는 것입니다.
- 초기화의 실패를 나타내기 위해 return nil을 작성하지만, 초기화 성공을 나타 내기 위해 return 키워드를 사용하지 않습니다 (문법적 약속)
열거형의 실패가능 생성자 (Apple 공식문서)
enum TemperatureUnit {
case kelvin
case celsius
case fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = TemperatureUnit.kelvin
case "C":
self = TemperatureUnit.celsius
case "F":
self = TemperatureUnit.fahrenheit
default:
return nil
}
}
}
let c: TemperatureUnit = TemperatureUnit.celsius // TemperatureUnit()
let f: TemperatureUnit? = TemperatureUnit(symbol: "F") // Optional 타입
원시값을 가진 열거형에 대한 실패가능 생성자(Apple 공식문서)
// 열거형의 원시값 설정 (실패가능 생성자의 구현과 유사)
enum TemperatureUnit: Character {
case kelvin = "K"
case celsius = "C"
case fahrenheit = "F"
}
// 원시값이있는 열거형은 자동으로 실패가능 생성자 init?(rawValue :)를 구현함 ==> 일치하면 인스턴스 생성, 아니면 nil
// 열거형(rawValue: 값)의 경우 열거형의 멤버 리턴
let fahrenheitUnit: TemperatureUnit? = TemperatureUnit(rawValue: "F") // .fahrenheit
print(fahrenheitUnit!) // 열거형의 멤버 리턴, fahrenheit
print(fahrenheitUnit?.rawValue) // 열거형 설정 값 리턴 Optional("F")
let celsiusUnit: TemperatureUnit? = .celsius
print(celsiusUnit!) // 열거형의 멤버 리턴 celsius
print(celsiusUnit?.rawValue) // 열거형 설정 값 리턴 Optional("C")
let u: TemperatureUnit? = TemperatureUnit(rawValue: "X") // nil
- 열거형의 실패가능 생성자는 원시값을 설정을 통해 유사하게 구현할 수 있습니다.
초기화 실패의 전달(Propagation of Initialization Failure)
(동일단계 - 델리게이트 어크로스)
- 실패가능 ===> 실패불가능 (호출/위임) (OK)
- 실패불가능 ===> 실패가능 (호출/위임) (X)
(상속관계 - 델리게이트 업)
- (하위) 실패불가능 ===> (상위) 실패가능 (호출/위임) (X)
- (하위) 실패가능 ===> (상위) 실패불가능 (호출/위임) (OK)
- 두 경우 모두 초기화 실패를 유발하는 다른 생성자에 위임하면
- 전체 초기화 프로세스가 즉시 실패하고 더 이상 초기화 코드가 실행되지 않음
- (한마디로) 실패불가능 생성자는 다른 실패가능 생성자를 호출 불가능합니다
-> 실패가능이 더 범위가 실패 불가능 보다 포함하는 범위가 크기 때문에, 실패 불가능에서는 실패 가능 생성자를 호출할 수 없습니다.
동일단계(델리게이트 어크로스)
//실패가능 생성자 -> 실패불가능 생성자 호출 (가능)
struct Item {
var name = ""
init() {
}
init?(name: String) {
self.init()
}
}
//실패불가능 생성자 -> 살패가능 생성자 호출 (불가능)
struct Item {
var name = ""
init() {
self.init(name: "Test")
}
init?(name: String) {
self.name = name
}
}
- 실패가능 생성자가 실패불가능 생성자 호출하는 것은 가능합니다.
- 실패불가능 생성자는 다른 실패가능 생성자 호출하는 것은 불가능합니다.
상속관계에서의 호출(델리게이트 업) - Apple 공식문서
// 상속관계에서의 호출 예제
// 상품
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
//실패가능에서 실패가능 생성자 호출 가능
// 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 } // 상품의 갯수가 1보다 작으면 ====> 카트 항목 생성 실패
self.quantity = quantity // 수량이 한개 이상이면 ====> 초기화 성공
super.init(name: name) // "" (빈문자열이면) ====> 실패 가능 위임 OK
}
// 실패불가능 생성자에서 실패가능 생성자 호출은 불가능
// init(name: String, quantity: Int) {
// self.quantity = quantity
// super.init(name: name)
}
}
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("아이템: \(twoSocks.name), 수량: \(twoSocks.quantity)")
}
// 아이템: sock, 수량: 2
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("아이템: \(zeroShirts.name), 수량: \(zeroShirts.quantity)")
} else {
print("zero shirts를 초기화 불가(갯수가 없음)")
}
// zero shirts를 초기화 불가(갯수가 없음)
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("아이템: \(oneUnnamed.name), 수량: \(oneUnnamed.quantity)")
} else {
print("이름없는 상품 초기화 불가")
}
// 이름없는 상품 초기화 불가
상관관계에서 재정의 하기
- (상위) 실패가능(init?) ===> (하위) 실패불가능 생성자로 재정의(init) (OK) (강제 언래핑 활용 가능)
- (상위) 실패불가능(init) ===> (하위) 실패가능 생성자로 재정의(init?) (X)
// 서류라는 클래스 정의
class Document {
var name: String? // 옵셔널이기 때문에 자식에서 부모 클래스를 별도로 초기화 하지 않아도 괜찮음
init() {} // 서류 생성 (실패불가능) (이름은 nil로 초기화)
init?(name: String) { // 실패가능 생성자 ===> 이름이 "" 빈문자열일때, 초기화 실패(nil)
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() { // 재정의 (상위) 실패가능 =====> (하위) 실패불가능
// 이름을 넣어주기 때문에, 즉, nil이 나올 수 없기 때문에 강제 언래핑이 가능
super.init(name: "[Untitled]")! // 강제 언래핑(!)으로 구현
}
}
- 실패가능 생성자의 범위가 더 큼 (범위로 따져보기)
실패가능 생성자를 init!(파라미터)로 정의하기
- 일반적으로 init 키워드 (init?) 뒤에 물음표를 배치하여 적절한 유형의 선택적 인스턴스를 생성하는 실패 가능한 이니셜 라이저를 정의합니다.
- 적절한 유형의 암시적으로 언래핑된 옵셔널 인스턴스를 생성하는 실패 가능한 이니셜 라이저를 정의할 수 있습니다..
- 물음표 대신 init 키워드 (init!) 뒤에 느낌표
================================
- init? ====> init! 위임 가능
- init! ====> init? 위임 가능
- init? ====> init! 로 재정의 가능
- init! ====> init? 로 재정의 가능
=================================
- init ====> init! 위임 가능 (실패할 수도 있어집니다.)
파라미터가 없는 실패가능 생성자
class Example {
let isValid: Bool
init?() {
if Bool.random() { // 랜덤한 조건
self.isValid = true
} else {
return nil
}
}
}
if let instance = Example() {
print("객체 생성 성공: \(instance)")
} else {
print("객체 생성 실패")
}
- 파라미터가 없는 실패 가능 생성자를 만들 수는 있지만, 아무런 조건 없이 실패할 가능성이 없다면, 굳이 실패 가능 생성자로 만들 필요는 없습니다.
- 위 예제에서도 Bool의 상태를 random으로 받고 있지만, 실제 해당 코드를 사용할 일은 없을 것입니다.
'iOS > Swift 문법' 카테고리의 다른 글
구조체/클래스의 생성자/소멸자 비교 (0) | 2024.12.06 |
---|---|
초기화 해제 (소멸자, Deinitializer) (0) | 2024.12.06 |
필수생성자(Required Initializers) (0) | 2024.12.06 |
클래스의 지정, 편의 생성자 상속의 예외사항(자동상속) (0) | 2024.12.06 |
클래스의 지정, 편의 생성자의 상속과 재정의 (0) | 2024.12.06 |