TIL: 실패 가능 생성자(Failable Initializers)

Royce·2025년 3월 23일

Swift 문법

목록 보기
38/63

실패 가능 생성자(Failable Initializers)

  • 실패 가능 생성자는 인스턴스 생성에 실패할 수 있는 가능성을 가진 생성자를 의미한다
  • 인스턴스 생성이 실패할 경우, nil을 반환하여 생성이 실패했음을 나타낸다
  • 실패 가능 생성자는 클래스, 구조체, 열거형에서 모두 사용할 수 있다
  • 실패 가능 생성자는 init?() 또는 init!() 형태로 선언된다

실패 가능 생성자 정의 방법

  • init?(parameters) 형태로 정의하며, 실패 시 nil을 반환한다
  • 동일한 파라미터의 실패 가능 & 실패 불가능 생성자는 공존할 수 없다 (오버로딩 불가)
  • init!(parameters)init?()과 다르게 암시적으로 강제 언래핑된다 (위험하므로 잘 사용하지 않는다)

구조체에서의 실패 가능 생성자

  • 구조체에서 실패 가능 생성자는 구조체 인스턴스를 만들 때 유효하지 않은 값으로 인해 초기화가 실패할 수 있도록 정의한 생성자를 의미한다
  • init?() 형태로 선언되며, 조건을 만족하지 못하면 nil을 반환하여 초기화가 실패한다
  • 보통 사용자가 제공하는 입력 값의 유효성을 검사하고 잘못된 입력을 걸러낼 때 사용된다

구조체에서의 실패 가능 생성자 예제

// 구조체 정의 (User)
struct User {
    let username: String
    let age: Int
    
    // 실패 가능 생성자 정의
    init?(username: String, age: Int) {
        // 유효성 검사 - 이름이 비어있거나 나이가 음수일 때 초기화 실패
        if username.isEmpty || age < 0 {
            return nil  // 초기화 실패 시 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 구조체는 usernameage라는 두 저장 속성을 가진다
  • 실패 가능 생성자 init?(username:age:)는 이름이 빈 문자열이거나 나이가 음수일 경우 초기화가 실패하도록 설계
  • 초기화가 실패하면 nil을 반환하며, 성공할 경우 저장 속성에 값이 할당된다
  • 유효성 검사를 통해 잘못된 입력을 걸러낼 수 있도록 구현

열거형에서의 실패 가능 생성자

  • 열거형에서 실패 가능 생성자는 입력값이 정의된 열거형 케이스 중 하나로 변환될 수 없는 경우 초기화에 실패하도록 설계한 생성자이다
  • init?() 형태로 선언되며, 유효하지 않은 입력 값이 들어오면 nil을 반환한다
  • 사용자가 잘못된 값으로 열거형 인스턴스를 만들지 못하도록 방지하는 기능을 제공한다

열거형에서의 실패 가능 생성자 예제

// 열거형 정의 (ColorType)
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을 반환하여 초기화에 실패한다

원시값을 사용하는 열거형 예제

// 열거형 정의 (GradeLevel)
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을 반환한다

초기화 실패의 전달 (호출 관계) 예제

// 상위 클래스 정의 (Product)
class Product {
    var name: String
    
    // 실패 가능 생성자
    init?(name: String) {
        if name.isEmpty { 
            return nil  // 이름이 빈 문자열일 경우 초기화 실패
        }
        self.name = name
    }
}

// 하위 클래스 정의 (InventoryItem)
class InventoryItem: Product {
    var quantity: Int
    
    // 실패 가능 생성자
    init?(name: String, quantity: Int) {
        if quantity < 1 { 
            return nil  // 수량이 1보다 작으면 초기화 실패
        }
        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)
  • 강제 언래핑(!)을 사용하여 상위 실패 가능 생성자를 호출하면 실패 불가능으로 변환할 수 있다

상속 관계에서 재정의 예제

// 상위 클래스 정의 (Document)
class Document {
    var name: String?
    
    init() {}  // 실패 불가능 생성자
    
    init?(name: String) {  // 실패 가능 생성자
        if name.isEmpty { return nil }
        self.name = name
    }
}

// 하위 클래스 정의 (TextDocument)
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 ?? "이름 없음")  // 출력: Untitled

let doc2 = TextDocument(name: "")
print(doc2.name ?? "이름 없음")  // 출력: Untitled
  • Document 클래스는 두 가지 초기화 방식을 제공한다 (기본 초기화 및 실패 가능 초기화)
  • init?(name:) 생성자는 이름이 빈 문자열일 경우 초기화에 실패하도록 설계
  • TextDocument 클래스는 Document의 실패 가능 생성자를 실패 불가능 생성자로 재정의
  • 강제 언래핑 없이 안전하게 실패 가능 생성자를 재정의하는 방법을 사용
  • 상위의 실패 가능 생성자를 하위에서 실패하지 않도록 처리할 수 있다

실패 가능 생성자를 init!()로 정의

  • init!()init?()와 달리 암시적 강제 언래핑 (Implicitly Unwrapped Optionals) 형태로 정의된다
  • init!()는 실패할 수 있지만, 호출 후에는 강제 언래핑된 값처럼 사용된다
  • 일반적으로 init?()로 선언하는 것이 안전하며, init!() 사용은 권장되지 않는다
  • init?() ➡️ init!()로 변경하는 것은 가능하지만, 반대는 불가능하다

실패 가능 생성자를 init!()로 정의 예제

// 클래스 정의 (Vehicle)
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: "")  // nil 반환
  • Vehicle 클래스는 이름을 저장하는 강제 언래핑 실패 가능 생성자를 가지고 있다
  • init!(brand:)는 브랜드 이름이 빈 문자열일 경우 nil을 반환한다
  • 이 방식은 잘못 사용될 경우 런타임 에러를 발생시킬 수 있으므로 주의해서 사용해야 한다
  • 일반적으로 init?()를 사용하는 것이 더 안전하고 권장된다

요약

  • 실패 가능 생성자는 init?() 또는 init!()로 정의하며, 실패 시 nil을 반환한다
  • 구조체, 클래스, 열거형 모두 사용 가능하며, 원시값을 사용하는 열거형은 자동으로 init?(rawValue:)를 제공한다
  • 실패 가능 생성자는 유효성 검사를 통해 안전하게 인스턴스를 생성하는 데 유용하다
  • 초기화 실패의 전달은 상속 관계에서도 적용되며, 상위 초기화가 실패하면 전체 초기화도 실패한다
  • 상위의 실패 가능 생성자는 하위에서 실패 불가능으로 재정의할 수 있지만, 그 반대는 불가능하다
  • init!()는 실패 가능 생성자를 강제 언래핑으로 정의하는 방식이며, 잘못 사용하면 런타임 에러가 발생할 수 있다
profile
iOS 개발자 지망생

0개의 댓글