이니셜라이저 알아보기(1)
Swift에서 이니셜라이저는 크게 구조체 이니셜라이저와 클래스 이니셜라이저로 구분됩니다. 이러한 구분이 생긴 이유는 구조체와 달리 클래스는 상속을 지원하기 때문입니다. 그래서 클래스 이니셜라이저가 조금 더 복잡합니다. 총 2부로 나누어 각 이니셜라이저의 특징을 알아보도록 하겠습니다.
구조체 이니셜라이저
Swift에서 구조체, 클래스와 열거형을 다룰 때, 적지 않은 비중을 차지하는 이니셜라이저(Intializer)를 알아보겠습니다. 이니셜라이저를 구조체 혹은 클래스 내부 프로퍼티에 초기 값을 할당해주는 역할을 합니다. 이니셜라이저가 인스턴스 내 모든 프러퍼티에 적절한 초기값 할당에 실패한다면 인스턴스 생성은 실패하게 됩니다. 이니셜라이저는 인스턴스 생성을 위해 꼭 필요한 작업 중 하나입니다.
이니셜라이저 정의
이니셜라이저는 init
키워드로 정의할 수 있습니다.
1
2
3
4
5
6
7
struct Person {
var name: String
init() {
self.name = "김문어"
}
}
let person = Person()
Person
구조체 타입의 인스턴스를 생성하게 되면 name
프로퍼티에 ‘김문어’라는 값이 할당되게 됩니다. 그러면 우리는 해당 인스턴스를 자유롭게 사용할 수 있게 되는 거죠.
구조체(클래스) 내 모든 프로퍼티에 초기값이 할당되어 있다면 이니셜라이저를 생략해도 무방합니다.
1
2
3
4
struct Person {
var name: String = "김문어"
}
let person = Person()
구조체(클래스) 내 모든 프로퍼티에 초기값이 미리 할당되어 있다면 Swift 컴파일러는 별도 이니셜라이저가 정의되어 있지 않더라도 기본 이니셜라이저를 자동으로 생성해줍니다. 이렇게 생성된 기본 이니셜라이저를 이용해 인스턴스를 생성할 수 있습니다.
이니셜라이저 매개변수
이니셜라이저는 (당연하게도) 매개변수를 가질 수 있습니다. 인스턴스 생성과 동ㅅ에 name
프로퍼티에 우리가 원하는 이름으로 초기화를 하고 싶다면 이니셜라이저의 매개변수를 통해 이름을 전달하고 name
프로퍼티에 전달한 이름을 할당하면 됩니다.
1
2
3
4
5
6
7
struct Person {
var name: String
init(name: String) {
self.name = name
}
}
let person = Person(name: "김흰둥")
self
는 자기 자신 인스턴스를 나타내는 키워드입니다. Person
구조체에 정의된 name
프로퍼티와 이니셜라이저 매개변수 name
이 서로 이름이 같아 혼동되므로, self
키워드로 명확하게 특정지을 수 있습니다.
상수와 옵셔널 프로퍼티
Person
구조체에 상수가 정의되어 있다면 어떻게 초기화를 해야 할까요? 앞서 예제와 동일하게 초기화를 하면 됩니다만, 값이 처음 할당된 이후로 값을 변경할 수 없습니다.
1
2
3
4
5
6
7
8
9
struct Person {
var name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person = Person(name: "김문어", age: 15)
옵셔널 프러퍼티는 초기화를 해주지 않는다면 자동으로 nil
을 할당받습니다.
1
2
3
4
5
struct Dog {
var tag: String? // nil
init() { }
}
let person = Person()
이때, 옵셔널 프로퍼티를 변수가 아닌 상수로 선언하게 된다면 에러가 발생합니다. 아무래도 옵셔널 프로퍼티에 nil
이 할당된 이후에 값이 바뀌지 않는다면 아무런 의미가 없기 때문이라고 추측해봅니다.
멤버와이즈 이니셜라이저
구조체에 별도 이니셜라이저가 정의되어 있지 않다면 Swift 컴파일러는 멤버와이즈 이니셜라이저(Memberwise Intializer)를 자동으로 생성해줍니다. 멤버와이즈 이니셜라이저는 값이 할당되지 않은 프로퍼티를 매개변수로 전달해줄 수 있는 이니셜라이저입니다.
1
2
3
4
5
struct Person {
var name: String
var age: Int
}
let person = Person(name: "김문어", age: 15)
멤버와이즈 이니셜라이저는 구조체에서만 생성됩니다.
만약에 멤버와이즈 이니셜라이저와 별도 정의한 이니셜라이저를 함께 사용하고 싶다면 어떻게 하면 될까요? 확장에 별도 이니셜라이저를 정의하면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
struct Person {
var name: String
var age: Int
}
extension Person {
init(name: String) {
self.name = name
self.age = 0
}
}
let person1 = Person(name: "김흰둥")
let person2 = Person(name: "김문어", age: 15)
실패 가능한 이니셜라이저
초기화에 실패할 수도 있습니다. 가령, 유효하지 않은 범위에 속한 값이나 너무 긴 문자열로 초기화를 시도하면 인스턴스 생성에 실패하도록 할 수 있습니다. 실패 가능한 이니셜라이저(Falliable Intializer)는 init?
키워드로 정의할 수 있습니다. 실패 가능한 이니셜라이저는 초기화에 성공하면 옵셔널 인스턴스 생성하고, 실패하면 nil
을 반환합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Person {
var name: String
var age: Int
init?(name: String, age: Int) {
// 나이가 0 미만이거나 100 초과하면
if age < 0 || age > 100 {
return nil // 초기화 실패
}
// 초기화 성공
self.name = name
self.age = age
}
}
let person: Person? = Person(name: "김문어", age: -3)
초기화에 실패해야 한다며 nil
을 반환하면 됩니다. 그런데 실제로 nil
을 반환한다기 보다는 초기화에 실패했다는 또 다른 키워드로 생각하는 게 편합니다.
이니셜라이저 위임
코드의 중복을 줄이고 효율적인 로직을 위해 이니셜라이저를 위임(Delegation)할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
struct Person {
var name: String
var age: Int
init(name: String) {
//self.age = 100
self.init(name: name, age: 0)
}
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person = Person(name: "김지지")
한 가지 주의해야 할 점은 다른 이니셜라이저에게 초기화 작업을 위임하기 전에 내부 프로퍼티에 값을 임의로 할당할 수 없습니다. 왜냐하면 내부 프로퍼티에 값을 할당하게 되면 뒤이어 호출될 다른 이니셜라이저에 의해 값이 바뀔 가능성이 존재하기 때문입니다.
아울러, 실패 가능한 이니셜라이저는 실패 가능한 이니셜라이저에게만 초기화 작업을 위임할 수 있습니다. 별도 정의한 이니셜라이저도 실패 가능한 이니셜라아지롤 호출할 수 있지만, 초기화에 실패하게 된다면 크래시가 발생할 수 있다는 점을 명심해두어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Person {
var name: String
var age: Int
init?(name: String, age: Int) {
// 나이가 0 미만이거나 100 초과하면
if age < 0 || age > 100 {
return nil // 초기화 실패
}
// 초기화 성공
self.name = name
self.age = age
}
init?(name: String) {
self.init!(name: name, age: -3)
}
}
let person: Person? = Person(name: "김문어")
참고 자료
■ 야곰, ⌜스위프트 프로그래밍⌟, 한빛미디어, P.342~360, 20230909 ■ 애플 공식 개발자 문서, ⌜The Swift Programming Language⌟, 공식 페이지, 20230909