실패 가능 생성자(Failable Initializers)
- 실패 가능 생성자는 인스턴스 생성에 실패할 수 있는 가능성을 가진 생성자를 의미한다
- 인스턴스 생성이 실패할 경우,
nil을 반환하여 생성이 실패했음을 나타낸다
- 실패 가능 생성자는 클래스, 구조체, 열거형에서 모두 사용할 수 있다
- 실패 가능 생성자는
init?() 또는 init!() 형태로 선언된다
실패 가능 생성자 정의 방법
init?(parameters) 형태로 정의하며, 실패 시 nil을 반환한다
- 동일한 파라미터의 실패 가능 & 실패 불가능 생성자는 공존할 수 없다 (오버로딩 불가)
init!(parameters)는 init?()과 다르게 암시적으로 강제 언래핑된다 (위험하므로 잘 사용하지 않는다)
구조체에서의 실패 가능 생성자
- 구조체에서 실패 가능 생성자는 구조체 인스턴스를 만들 때 유효하지 않은 값으로 인해 초기화가 실패할 수 있도록 정의한 생성자를 의미한다
init?() 형태로 선언되며, 조건을 만족하지 못하면 nil을 반환하여 초기화가 실패한다
- 보통 사용자가 제공하는 입력 값의 유효성을 검사하고 잘못된 입력을 걸러낼 때 사용된다
구조체에서의 실패 가능 생성자 예제
struct User {
let username: String
let age: Int
init?(username: String, age: Int) {
if username.isEmpty || age < 0 {
return nil
}
self.username = username
self.age = age
}
}
let validUser = User(username: "Alice", age: 25)
let invalidUser1 = User(username: "", age: 25)
let invalidUser2 = User(username: "Bob", age: -1)
User 구조체는 username과 age라는 두 저장 속성을 가진다
- 실패 가능 생성자
init?(username:age:)는 이름이 빈 문자열이거나 나이가 음수일 경우 초기화가 실패하도록 설계
- 초기화가 실패하면
nil을 반환하며, 성공할 경우 저장 속성에 값이 할당된다
- 유효성 검사를 통해 잘못된 입력을 걸러낼 수 있도록 구현
열거형에서의 실패 가능 생성자
- 열거형에서 실패 가능 생성자는 입력값이 정의된 열거형 케이스 중 하나로 변환될 수 없는 경우 초기화에 실패하도록 설계한 생성자이다
init?() 형태로 선언되며, 유효하지 않은 입력 값이 들어오면 nil을 반환한다
- 사용자가 잘못된 값으로 열거형 인스턴스를 만들지 못하도록 방지하는 기능을 제공한다
열거형에서의 실패 가능 생성자 예제
enum ColorType {
case red, blue, green, yellow
init?(colorName: String) {
switch colorName.lowercased() {
case "red":
self = .red
case "blue":
self = .blue
case "green":
self = .green
case "yellow":
self = .yellow
default:
return nil
}
}
}
let validColor = ColorType(colorName: "Red")
let invalidColor = ColorType(colorName: "Pink")
ColorType은 문자열을 입력받아 유효한 색상을 초기화하는 열거형이다
init?(colorName:)는 유효하지 않은 값이 입력되면 nil을 반환한다
- 사용자가 입력하는 값이 지정된 값들 외의 경우 초기화가 실패하도록 설계
원시값을 사용하는 열거형
- 열거형에 원시값 (
Raw Value) 를 지정하면, 각 케이스가 특정 값과 연결된다
- Swift에서는 원시값이 있는 열거형에 대해 자동으로 실패 가능 생성자
init?(rawValue:)를 제공한다
- 원시값과 매칭되지 않는 값이 입력되면,
nil을 반환하여 초기화에 실패한다
원시값을 사용하는 열거형 예제
enum GradeLevel: String {
case freshman = "Freshman"
case sophomore = "Sophomore"
case junior = "Junior"
case senior = "Senior"
}
let validGrade = GradeLevel(rawValue: "Junior")
let invalidGrade = GradeLevel(rawValue: "Graduate")
GradeLevel은 문자열 원시값을 가지는 열거형이다
- 원시값이 있는 열거형은 자동으로
init?(rawValue:)라는 실패 가능 생성자가 제공된다
- 유효하지 않은 원시값이 전달되면
nil을 반환하여 초기화에 실패하게 된다
초기화 실패의 전달 (호출 관계)
- 실패 가능 생성자가 실패하면, 전체 초기화 과정이 즉시 중단되고
nil을 반환한다
- 실패 가능 생성자는 다른 실패 가능 생성자를 호출할 수 있지만, 실패 불가능 생성자는 실패 가능 생성자를 호출할 수 없다
- 상속 관계에서도 실패 가능 생성자가 상위 클래스의 실패 가능 생성자를 호출할 수 있다
- 만약 상위 클래스의 초기화가 실패하면, 전체 초기화도 즉시 실패하고
nil을 반환한다
초기화 실패의 전달 (호출 관계) 예제
class Product {
var name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
class InventoryItem: Product {
var quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 {
return nil
}
self.quantity = quantity
if let _ = super.init(name: name) {
print("InventoryItem 초기화 성공")
} else {
return nil
}
}
}
if let validItem = InventoryItem(name: "Apple", quantity: 5) {
print("아이템: \(validItem.name), 수량: \(validItem.quantity)")
} else {
print("초기화 실패")
}
if let invalidItem = InventoryItem(name: "", quantity: 10) {
print("아이템: \(invalidItem.name), 수량: \(invalidItem.quantity)")
} else {
print("초기화 실패: 이름이 잘못되었음")
}
Product 클래스는 이름을 저장하는 실패 가능 생성자를 가진다
InventoryItem 클래스는 Product를 상속하며, 수량을 추가로 관리한다
- 하위 클래스의 실패 가능 생성자는
super.init()을 호출하여 상위 클래스의 초기화 성공 여부를 확인한다
- 만약 상위 클래스의 초기화가 실패하면, 전체 초기화도 즉시 실패하고
nil을 반환한다
- 이 과정은 상속 관계에서 초기화 실패의 전달 방식이다
상속 관계에서 재정의
- 실패 가능 생성자는 상속 관계에서 재정의(
override)할 수 있다
- 재정의 규칙은 다음과 같다
• 상위의 실패 가능 생성자를 하위에서 실패 불가능 생성자로 재정의할 수 있다 (OK)
• 상위의 실패 불가능 생성자를 하위에서 실패 가능 생성자로 재정의할 수 없다 (X)
- 강제 언래핑(
!)을 사용하여 상위 실패 가능 생성자를 호출하면 실패 불가능으로 변환할 수 있다
상속 관계에서 재정의 예제
class Document {
var name: String?
init() {}
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class TextDocument: Document {
override init(name: String) {
super.init()
self.name = name.isEmpty ? "Untitled" : name
}
override init() {
super.init()
self.name = "Untitled"
}
}
let doc1 = TextDocument()
print(doc1.name ?? "이름 없음")
let doc2 = TextDocument(name: "")
print(doc2.name ?? "이름 없음")
Document 클래스는 두 가지 초기화 방식을 제공한다 (기본 초기화 및 실패 가능 초기화)
init?(name:) 생성자는 이름이 빈 문자열일 경우 초기화에 실패하도록 설계
TextDocument 클래스는 Document의 실패 가능 생성자를 실패 불가능 생성자로 재정의
- 강제 언래핑 없이 안전하게 실패 가능 생성자를 재정의하는 방법을 사용
- 상위의 실패 가능 생성자를 하위에서 실패하지 않도록 처리할 수 있다
실패 가능 생성자를 init!()로 정의
init!()는 init?()와 달리 암시적 강제 언래핑 (Implicitly Unwrapped Optionals) 형태로 정의된다
init!()는 실패할 수 있지만, 호출 후에는 강제 언래핑된 값처럼 사용된다
- 일반적으로
init?()로 선언하는 것이 안전하며, init!() 사용은 권장되지 않는다
init?() ➡️ init!()로 변경하는 것은 가능하지만, 반대는 불가능하다
실패 가능 생성자를 init!()로 정의 예제
class Vehicle {
var brand: String
init!(brand: String) {
if brand.isEmpty { return nil }
self.brand = brand
}
}
let validCar = Vehicle(brand: "Tesla")
let invalidCar = Vehicle(brand: "")
Vehicle 클래스는 이름을 저장하는 강제 언래핑 실패 가능 생성자를 가지고 있다
init!(brand:)는 브랜드 이름이 빈 문자열일 경우 nil을 반환한다
- 이 방식은 잘못 사용될 경우 런타임 에러를 발생시킬 수 있으므로 주의해서 사용해야 한다
- 일반적으로
init?()를 사용하는 것이 더 안전하고 권장된다
요약
- 실패 가능 생성자는
init?() 또는 init!()로 정의하며, 실패 시 nil을 반환한다
- 구조체, 클래스, 열거형 모두 사용 가능하며, 원시값을 사용하는 열거형은 자동으로
init?(rawValue:)를 제공한다
- 실패 가능 생성자는 유효성 검사를 통해 안전하게 인스턴스를 생성하는 데 유용하다
- 초기화 실패의 전달은 상속 관계에서도 적용되며, 상위 초기화가 실패하면 전체 초기화도 실패한다
- 상위의 실패 가능 생성자는 하위에서 실패 불가능으로 재정의할 수 있지만, 그 반대는 불가능하다
init!()는 실패 가능 생성자를 강제 언래핑으로 정의하는 방식이며, 잘못 사용하면 런타임 에러가 발생할 수 있다