타입에 저장된 프로퍼티에 초기값을 설정하고 초기 설정을 수행한다.
공식문서보고 정리하고 싶었는데 어려움..
결국 소들이님 블로그 봄
초기화(initalizers)란
구조체 / 열거형 / 클래스의 인스턴스를 생성하는 것이 초기화로,
초기화의 역할은 모든 프로퍼티를 기본값으로 초기화하는 것이다.
인스턴스 내 기본값이 존재하지 않는 프로퍼티가 있을 경우(초기화가 끝나는 시점에 모든 프로퍼티가 기본값을 가지고 있지 않다면),
초기화에 실패하고 인스턴스는 생성되지 않는다.
구조체(Struct)를 초기화 하는 방법
1. 선언과 동시에 프로퍼티에 기본값 넣어주기
struct Human {
let name: String = "Kaia"
let age: Int = 32
}
name이란 프로퍼티에 kaia라는 값을, age에는 32란 값을 직접 선언과 동시에 기본값을 넣어주어
선언과 동시에 기본값으로 초기화를 시킬 수 있다.
2. 프로퍼티의 타입을 옵셔널(Optional) 타입으로 설정하기
struct Human {
let name: String?
let age: Int?
}
name, age 값을 옵셔널 타입으로 설정하는 것 또한 초기화를 진행해준 것
따라서 만약 값이 있을지도 없을지도 모를 경우에는 위처럼 옵셔널 타입으로 설정하면 된다.
그러면 초기화를 할 때 옵셔널 타입인 name과 age는 자동으로 nil로 초기화 됨.
3. init 함수에서 값을 설정해주기
struct Human {
let name: String
let age: Int
init(name: String) {
self.name = name
self.age = 32
}
}
초기화를 진행하는 함수를 init 함수라고 한다(생성자라고도 부름)
따라서 위에서처럼 프로퍼티를 선언할 때 어떤 값을 지정해줄 수도 있지만,
생성자 안에서 해당 프로퍼티들을 초기화 시킬 수도 있다.
중요한 건 init(초기화) 함수가 종료되기 전까지 모든 프로퍼티가 기본 값을 가져야 한다는 것.
init 함수에서 파라미터를 직접 받아서 초기화를 진행할 수 있다.
초기화는 모든 프로퍼티가 값을 가져야만 초기화에 성공해서 인스턴스를 생성한다고 했는데
struct HumanStruct {
let name: String
let age: Int
}
class HumanClass { // initializer 갖지 않는다고 에러남
let name: String
let age: Int
}
똑같이 name, age를 초기화 하지 않은 구조체와 클래스를 만들었을 경우,
클래스만 initalizers를 갖지 않는다면서 에러가 난다.
초기값을 갖기 위해서는 선언과 동시에 기본값을 갖거나, 혹은 옵셔널 타입으로 선언되어야 한다고 했는데
위에서 name, age는 기본값을 갖거나 옵셔널 타입도 아니다.
이 경우에는 init 함수를 직접 작성해서, 함수 안에서라도 반드시 프로퍼티 초기화를 지정했어야 했는데 해주지 않았으니
클래스처럼 초기화없다고 에러가 나는것이 정상인데
클래스만 에러가 나는 이유는
구조체는 Memberwise Initalizers 라는 초기화를 기본으로 제공하기 때문이다.
구조체(Struct)에서만 제공하는 Memberwise Initalizers
구조체가 자동으로 제공하는 생성자로, 파라미터를 통해 모든 프로퍼티의 초기화를 가능하게 한다.
이 말은 구조체의 경우, 프로퍼티의 초기화를 따로 지정하지 않을 경우에 초기화 되지 않은 프로퍼티를 초기화할 수 있는 init함수를 자동으로 제공해준다.
memberwise initalizers로 제공되는 생성자의 파라미터는, 프로퍼티가 선언된 순서와 갯수에 맞추어 생성된다. 이때 파라미터의 이름은 프로퍼티의 이름으로 설정된다.
struct Human {
let name: String
let age: Int
}
Human.init(name: "Kaia", age: 32)
Human.init(age: 32, name: "Kaia") // 에러
초기화되지 않은 프로퍼티의 개수가 2개(name, age)이니 파라미터로 2개의 값을 받는 것이고,
프로퍼티 선언 순서가 name 선언 후 age 선언이기 때문에 생성자의 파라미터는 프로퍼티의 선언된 순서를 지키기 때문에
age를 name보다 먼저 파라미터로 가져서는 안된다.
또한 내가 name, age란 이름으로 프로퍼티를 선언했기 때문에 파라미터 이름 또한 name, age로 생성된다.
프로퍼티가 let으로 선언되어있고, 이미 초기화가 되어있는 상태면 생성자 목록에서 제외된다.
struct Human {
let name: String = "Kaia"
let age: Int
}
Human.init(age: 32)
name이란 프로퍼티가 let으로 선언되어있어, 초기값을 가지고있는 경우에는 생성자의 파라미터에서 제외된다.
let 으로 선언될 경우, 이미 초기값을 가진 경우 값을 수정할 수 없기 때문에
어차피 memberwise initalizers를 통해 파라미터로 값을 받아도 name이란 프로퍼티의 값을 변경할 수 없기 때문이다.
프로퍼티가 var로 선언 되어 있고, 이미 초기화가 되어있는 상태면 해당 프로퍼티를 초기화하는 생성자와, 제외된 생성자 두개를 제공한다.
struct Human {
var name: String = "Kaia"
}
Human.init() // name이 제외된 생성자
Human.init(name: "Kaia") // name을 초기화 시킬 수 있는 생성자
name이란 프로퍼티 하나로 예를 보면,
name이란 프로퍼티가 var로 선언되어 있기 때문에
초기값을 갖더라도 이니셜라이저를 통해 값을 변경할 수 있어서
이때는, name이란 프로퍼티를 초기화 할 수 있는 생성자 한 개와 초기화 하지 않아도 되는 생성자 한 개가 각각 제공된다.
struct Human {
var name: String = "Kaia"
var age: Int
var alias: String = "aki"
}
Human.init(age: 32)
Human.init(name: "Kaia", age: 32
Human.init(age: 32, alias: "aki")
Human.init(name: "Kaia", age: 32, alias: "aki")
만약 위와 같이 프로퍼티를 선언할 경우,
저렇게 다양한 memberwise initializers가 제공된다.
생성자를 "직접" 생성한 경우, memberwise initalizers는 더이상 제공되지 않는다.
struct Human {
var name: String
var alias: String
init(name: String) {
self.name = name
self.alias = name
}
}
여기서 init(name:) 을 직접 정의하면, Swift는 기본 제공하는 memberwise initializer를 제거한다.
즉, 아래 생성자는 사용할 수 없다.
Human(name: "Kaia", alias: "aki") // 오류
init(name: String, alias: String)을 자동으로 제공하지 않음.
하지만 직접 정의한 init(name:)은 여전히 사용할 수 있음
Human(name: "Kaia") // 가능
이 init(name:)은 memberwise initializer가 아니라, 사용자가 직접 만든 생성자이다.
만약 직접 init 함수도 구현하고 싶고, memberwise initializers도 사용하고 싶다면
이때는 extension을 활용하면 된다.
struct Human {
var name: String
var alias: String
}
extension Human {
init(name: String) {
self.name = name
self.alias = name
}
}
Human.init(name: "Kaia")
Human.init(name: "Kaia", alias: "aki")
이렇게 직접 구현한 init 함수를 extension으로 뺄 경우,
이때는 내가 정의한 init과 memberwise initalizers 모두 사용할 수 있다.
프로퍼티 중 하나라도 private일 경우, memberwise initalizers의 접근제어자 또한 private이다.
struct Human {
private var name: String
var alias: String
}
선언된 프로퍼티 중 하나라도 private 접근 제어자를 가질 경우,
해당 memberwise initalizers 또한 접근 제어자가 private이 된다.
따라서 내부에서만 사용할 수 있고, 외부에서 더이상 사용할 수 없음.
클래스(Class)를 초기화 하는 방법
클래스를 초기화하는 방법은 구조체랑 똑같다
선언과 동시에 프로퍼티에 기본값 넣어주기
class Human {
let name: String = "Kaia"
let age: Int = 32
}
name이란 프로퍼티에 Kaia란 값을, age에는 32란 값을 직접 선언과 동시에 기본값을 넣어주며 초기화
이렇게 선언과 동시에 기본값으로 초기화를 시킬 수 있음
프로퍼티 타입을 옵셔널(Optional) 타입의 변수로 설정
class Human {
var name: String?
var age: Int?
}
name, age 값을 옵셔널 타입의 변수로 설정하는 것 또한 초기화를 진행해준 것
따라서 만약 값이 있을지도 없을지도 모를 경우에는 옵셔널 타입으로 설정하면 되고
초기화를 할 때 옵셔널 타입인 name과 age는 자동으로 nil로 초기화가 된다.
변수만 가능 상수는 안됨
let으로 선언하고 기본값을 안주면 에러가 뜬다.
init 함수에서 값을 설정해주기
class Human {
var name: Stirng?
let nickName: String = "Kaia"
let age: Int
init(name: String) {
self.age = 32
}
}
만약 하나의 프로퍼티라도 기본값을 지니지 않거나, 옵셔널 타입의 변수가 아니라면
이땐 초기화를 진행하는 함수인 init 함수(생성자)를 통해 나머지도 초기화를 진행해줘야 한다.
따라서 위처럼 생성자 안에서 해당 프로퍼티들을 초기화시킬 수도 있다.
(만약 모든 프로퍼티가 기본값을 지니거나 옵셔널 타입의 변수거나해서 초기값을 갖는다면 그땐 생성자를 따로 선언해줄 필요가 없다.)
init(초기화) 함수가 종료되기 전까지 모든 프로퍼티가 기본 값을 가져야하기 때문에
위처럼 init 함수에서 파라미터를 직접 받아서 초기화를 진행할 수 있다.
기본값을 지정하든, 옵셔널 타입의 변수로 설정하든, init 함수에서 작성하든 셋을 다 쓰든
결론은 모든 프로퍼티는 초기화 시점에 초기값을 갖고 있어야 한다.
구조체는 기본값 초기화도 안하고, init 함수도 안만들어도 에러가 안나는데 클래스는 에러가 남
클래스는 Memberwise Initializers 제공을 안하기 때문에
프로퍼티의 초기값을 지정시켜 주어야 함
클래스의 Initializers
클래스는 initializers를 두 가지로 나누어서 볼 수 있다.
지정 생성자(Designated Initalizers)
클래스의 모든 프로퍼티를 초기화 하는 생성자
우리가 썼던 모든 생성자의 풀네임은 지정 생성자(Designated Initializers)
이 Designated Initalizers의 핵심은 두 가지이다.
1. 해당 생성자 메서드가 종료되기 전까지, 생성자 안에 모든 프로퍼티는 "초기값"을 지니고 있어야 한다.
2. 반드시 super 클래스의 생성자를 호출해야 한다.
class Human {
let name: String
init(name: String) {
self.name = name
}
}
class Kaia: Human {
let alias: String
init(alias: String) { // error! 'super.init' isn't called on all....
self.alias = alias
}
}
Designated Initalizers의 핵심 중 또 한가지는
상속받은 서브 클래스에서 Designated Initalizers를 작성할 경우,
반드시 슈퍼 클래스의 Initializers를 호출해주어야 한다.
만약 위처럼 내 프로퍼티만 모두 초기화 시키는 init 함수를 작성할 경우,
내 부모(슈퍼) 클래스의 프로퍼티 중 만약 기본값이나 옵셔널 타입의 변수가 아니라서 초기값을 가지지 않았다면 초기화 안된다..
class Human {
let name: String
init(name: String) {
self.name = name
}
}
class Kaia: Human {
let alias: String
init(alis: String) {
self.alias = alias
super.init(name: alias)
}
}
따라서 이렇게 super 클래스의 생성자를 init에서 직접 호출해줘서 부모 클래스의 초기화도 진행해주어야 에러가 사라진다.
편의 생성자(Convenience Initializers)
모든 프로퍼티를 초기화할 필요 없는 생성자로 반드시 다른 초기화를 호출시켜야 한다.
이때 슈퍼 클래스의 생성자를 호출시킬수는 없고
같은 클래스 내에 있는 Convenience Initializers나 Designated Initializers를 호출시켜야 한다
최종적으로는 같은 클래스 내의 Designated Initializers가 호출되어야 한다.
Convenience Initializers란 단어를 보면 편의 초기화라는 뜻인데
따라서 이 Convenience Initializers는 위에서 말했던 모든 프로퍼티를 초기화 해주는 Designated Initializers의
파라미터 중 원하는 값을 기본값으로 설정해서 도와주는 역할이다.
class Kaia {
var name: String
var nickName: String
init(name: String, nickName: String) {
self.name = name
self.nickName = nickName
}
convenience init(name: String) {
self.init(name: name, nickName: "unknown")
}
}
이런식으로
convenience Initializers의 가장 큰 핵심은
반드시 같은 클래스(슈퍼 클래스X)에 있는 "다른 초기화"를 또 호출 시켜야 한다는 것이다.
그것이 convenience든 Designated든
근데 Convenience Initializers는 Designated Initalizers를 보조하는 역할이기 때문에
최종적으로는, 같은 클래스 내에 있는 Designated Initializers를 호출시켜야 한다.
Convenience Initializers는 보조 생성자이기 때문에, 굳이 모든 프로퍼티를 초기화할 필요가 없음
그럼 초기화 안 된 프로퍼티가 있으면?
그건 최종적으로 같은 클래스 내에 있는 Designated Initializers를 호출시키기 때문에 문제 없다.
Designated Initializers는 모든 프로퍼티를 초기화 시킬거니까.
Convenience init은 자기들끼리 호출해도 결국 마지막에는 Designated로 귀결된다.
Convenience init을 사용하려면 그전에 먼저 Designated initializers가 필수적으로 구현되어 있어야 한다.
그래야 최종적으로 부를 수 있음.
만약, Convenience initializers에서 최종적으로 Designated Initializers를 호출하지 않으면 에러남
정리
생성자의 역할은 모든 프로퍼티의 값들을 초기화하는 것
하나라도 초기화되지 않은 프로퍼티가 있으면 인스턴스 생성 안됨
클래스의 생성자는 총 두가지
Designated Initializers
init 하고 썼던 초기화로 init 함수가 끝나기 전엔 모든 프로퍼티의 값이 초기화되어 있어야 한다.
(모든 프로퍼티가 기본 값을 갖거나 옵셔널 타입의 변수로 선언된 경우에는 작성하지 않아도 됨.)
Convenience Initializers
Designated Initializers의 초기화를 보조해주는 역할의 초기화
Designated Initializers의 파라미터 중 일부를 기본값으로 설정해서 호출할 수 있다.
이 Convenience Initializers는 반드시 다른 초기화 메서드를 호출시켜주어야 하는데,
이때 다른 초기화 메서드란, Convenience / Designated 상관없이 같은 클래스 내에 있는 초기화 메서드이기만 하면 된다.
다만 최종적으로 반드시 같은 클래스(계층)내에 있는 Designated Initializers가 호출되어야 한다.
'iOS 관련 공부' 카테고리의 다른 글
타입 캐스팅 (Type Casting) (0) | 2025.02.21 |
---|---|
속성(Property)과 메서드(Method) - 1 (0) | 2025.02.16 |
클래스와 구조체 (0) | 2025.02.06 |
스토리보드 사용해서 앱 만들기 (0) | 2025.02.05 |
iOS Architecture / Framework (0) | 2025.02.02 |