야곰 님의 '스위프트 프로그래밍' 을 정리한 것입니다.
저번 포스트 Swift: 인스턴스 생성 1 에서는 프로퍼티 기본값, 이니셜라이저 매개변수, 옵셔널 프로퍼티 타입, 상수 프로퍼티에 대해서 알아보았다. 이번 포스트 에서는 인스턴스를 생성할 때 설정가능한 다른 옵션들에 대해 알아보자.
기본 이니셜라이저 / 멤버와이즈 이니셜라이저
사용자 정의 이니셜라이저를 정의해주지 않았을 때 클래스와 구조체는 프로퍼티에 기본값이 지정되어 있다는 전제 하에 기본 이니셜라이저를 사용한다.
기본 이니셜라이저는 프로퍼티 기본 값(Default Value)로 프로퍼티를 초기화해서 인스턴스를 생성한다.
프로퍼티 하나 때문에 이니셜라이저를 추가하거나 변경하는 일이 발생하기 때문에 구조체는 사용자 정의 이니셜라이저를 구현하지 않으면 프로퍼티의 이름으로 매개변수를 갖는 이니셜라이저인 멤버와이즈 이니셜라이저를 자동으로 생성해준다. 하지만 클래스는 멤버와이즈 이니셜라이저를 제공해주지 않는다.
struct Point {
var x: Double = 0.0
var y: Double = 0.0
}
struct Size {
var width: Double = 0.0
var height: Double = 0.0
}
let point: Point = Point(x: 0, y: 0) //멤버와이즈 이니셜라이저 사용
let size: Size = Size(width: 50.0, height: 50.0)
저장 프로퍼티에 이미 기본값이 있는 경우 다음과 같이 필요한 매개변수만 사용하여 초기화 할 수도 있다.
let point: Point = Point(y: 0)
let size: Size = Size(width: 50.0)
초기화 위임
구조체와 열거형은 값 타입이기 때문에 코등의 중복을 피하기 위해 이니셜라이저가 다른이니셜라이저에게 일부 초기화를 위임하는 초기화 위임을 간단한게 구현할 수 있지만, 클래스는 상속을 지원하기 때문에 간단한 초기화 위임도 할 수 없다. 여기서는 구조체와 열거형의 초기화 위임만 알아보자.
값 타입에서 이니셜라이저가 다른 이니셜라이저를 호출하기 위해서는 self.init을 사용하면 된다. self.init은 이니셜라이저 안에서만 사용할 수 있다.
self.init을 사용한 다는 것은 사용자 이니셜라이저를 사용하고 있다는 의미이고, 사용자 이니셜라이저를 정의했다는 것은 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 사용할 수 없다는 것을 의미하므로 최소 두 개 이상의 사용자 정의 이니셜라이저를 정의해야한다.
enum Student {
case elementary, middle, high
case none
init() {
self = .none
}
init(koreanAge: Int) { // 첫 번째 사용자 정의 이니셜라이저
switch koreanAge {
case 8...13:
self = .elementary
case 14...16:
self = .middle
case 17...19:
self = .high
default:
self = .none
}
}
init(bornAt: Int, currentYear: Int) { // 두 번째 사용자 정의 이니셜라이저
self.init(koreanAge: currentYear - bornAt + 1)
}
}
두 번째 사용자 정의 이니셜라이저에서 태어난 해와 현재 연도를 매개변수로 받아 나이로 계산한 뒤 첫 번째 이니셜라이저에 초기화를 위임 함으로 써 코드를 중복으로 작성하지 않고 효율적으로 여러 case의 이니셜라이저를 만들 수 있다.
실패 가능한 이니셜라이저
이니셜라이저를 통해서 인스턴스를 초기화 할 수 없는 여러가지 예외 상황이 있다. 이니셜라이저의 전달인자로 잘못된 값이나 적절하지 못한 값을 전달하였을 때가 그러하다.
이니셜라이저를 정의할 때 이러한 실패 가능성을 내포한 이니셜라이저를 실패 가능한 이니셜라이저(Failable initializer)라고 한다.
클래스, 구조체, 열거형에서 모두 정의할 수 있다. 실패가능한 이니셜라이저는 실패했을 때 nil을 반환하므로 반환 타입이 옵셔널로 지정된다. init? 키워드를 사용한다.
초기화에 실패 햇을 때는 return nil 을, 성공했을 때는 return을 적어 성공과 실패를 표현할 뿐 실제 값을 반환하지는 않는다.
실패하지 않는 이니셜라이저와 실패가능한 이니셜라이저는 이름이 같은 매개변수 타입을 갖도록 정의할 수 없음에 주의하자.
class Person {
let name: String
var age: Int? //옵셔널 선언
init?(name: String) { //Failable Initializer
if name.isEmpty {
return nil
}
self.name = name
}
init?(name: String, age: Int) { //Failable Initializer
if name.isEmpty || age < 0 {
return nil
}
self.name = name
self.age = age
}
}
실패 가능한 이니셜라이저는 특히 열거형에서 유용하게 사용할 수 있다. 특정 case에 맞지 않는 값이 들어오면 생성에 실패할 수 있다.
혹은 rawValue로 초기화 할 때, 잘못된 rawValue가 전달된다면 열거형 인스턴스를 생성하지 못할수도 있으므로 rawValue를 이용한 이니셜라이저는 기본적으로 실패 가능한 이니셜라이저로 제공된다.
함수를 사용한 프로퍼티 기본값 설정
사용자 정의 연산을 통해 저장 프로퍼티 기본값을 설정하기 위해서 클로저나 함수를 사용할 수 있다.
인스턴스를 초기화 할 때 함수나 클로저가 호출되면서 연산 결괏값을 프로퍼티 기본값으로 제공해준다. 따라서 반환타입이 프로퍼티의 타입과 일치해야한다.
만약 프로퍼티 기본값 설정을 위해 클로저를 사용한다면, 클로저가 실행되는 시점은 인스턴스의 다른 프로퍼티가 초기화되기 이전이라는 것에 주의해야한다. 즉 클로저 내부에서 다른 프로퍼티를 사용할 수 없다는 의미이다.
class SchoolClass {
var students: [Student] = {
//새로운 인스턴스를 생성하고 사용자 정의 연산 후 반환한다.
//반환된 타입은 [Student]타입이어야 한다.
var arr: [Student] = [Student]()
for num in 1...15 {
var student: Student = Student(name: nil, number: num)
arr.append(student)
}
return arr
}()
}
iOS에서 UI를 구성할 때, UI컴포넌트를 클래스의 프로퍼티로 구현하고, UI컴포넌트의 생성과 동시에 컴포넌트의 프로퍼티를 기본적으로 설정할 때 유용하다.
이렇게 다양한 인스턴스 초기화 기법을 통해 원하는 대로 인스턴스 초기화를 구현할 수 있다.