1. memberwise initializers
1-1) 해당 코드에서 왜 클래스만 에러가 나는 것일까?
인스턴스 내 기본값이 존재하지 않는 프로퍼티가 있을 경우, 초기화에 실패하고 인스턴스는 생성되지 않는다.
두 구조체와 클래스 모두 저장 프로퍼티를 초기화하지 않은 상태이다.
근데 왜 클래스에서만 에러가 나는지 설명하시오!
struct SoptMember{
let name: String
let part: String
}
class SoptMember{ // error: Class 'SoptMember' has no initializers
let name: String
let part: String
}
memberwise initializers에 대해 공부해보면 될듯?
그럼 class에서는 왜 멤버와이즈 이니셜라이즈 안되는지 등등 좀 공부해봅시다
1-2) 구조체에서는 에러가 발생하지 않지만, class에서는 에러가 발생하는 이유
구조체와 클래스의 가장 큰 차이점! 은 초기화 구문이 있냐 없냐! 라고 합니다.
구조체의 경우 저장 프로퍼티를 갖고있을 때 기본적으로 제공되는 초기화 구문이 있기 때문에, 따로 초기화 구문을 정의해주지 않아도 된다고 합니다.
하지만, 클래스의 경우 기본 초기화 구문을 제공해주지 않기 때문에 초기화 구문을 지정해주지 않으면 에러가 발생한다고 합니다.
error: Class 'SoptMember' has no initializers
위의 error 코드에서도 알 수 있듯이, 초기화 구문 ‘init()’ 을 정의해주지 않아서 생긴 문제임을 알 수 있어요.
class SoptMember {
let name: String
let part: String
init(name: String, part: String) {
self.name = name
self.part = part
}
}
let soptMember = SoptMember(name: "name", part: "part")
따라서 위에서처럼, init구문을 지정해주고 코드를 작성해주어야 에러가 발생하지 않아요!
1-3) memberwise initializers 그게 뭐길래?
구조체는 저장 프로퍼티를 자동으로 초기화하기 위해 멤버와이즈 초기화자(memberwise initializers)를 제공해줍니다. 이는 자동으로 생성하는 부분이며! 매번 초기화 구문을 작성해야하는 귀찮음을 덜어준다고 해요..!
1-4) Class에는 왜 memberwise initializers 가 없는걸까?
예전에 배웠던 기억을 되살려보자면, 구조체는 값타입이고, 클래스는 참조타입이라는 것을 알 수 있는데!
이러한 참조타입의 특성 때문에 초기화 상황에서 복잡한 상황을 처리해야해서 직접 초기값을 적어주거나, 초기화자를 사용해야 한다고 합니다.
그렇다면 복잡한 상황이 뭘까요???
- 상속
- 참조카운팅 → 이렇게 복잡한 문제를 처리해주는 것이라고 하네요.
암튼! Class의 경우 직접 프로퍼티에 대한 초기값을 제공하거나, 명시적으로 초기화자(init)를 작성해주어야 한다고 합니다!
2. 속성의 관점에서
2-1) 해당 코드에서 왜 에러가 나는 걸까요?
class SoptMember {
var name: String
var part: String
init(name: String, part: String) {
self.name = name
self.part = part
}
}
class partLeader: SoptMember {
var alias: String
init(alias: String) {// error! 'super.init' isn't called on all paths before returning from initializer
self.alias = alias
}
init(alias: String) {
self.alias = alias
super.init(name: "", part: "") //
}
}
2-2) 에러가 나는 이유를 공부해봅시다
코드를 보면! partLeader의 경우 SoptMember를 상속받은 것을 알 수 있는데요.
스위프트에서는 상속받은 클래스를 초기화할 때 항상 부모 클래스의 초기화자를 호출해야 한다고 합니다.
이는 super.init을 통해 이루어집니다. 따라서, partLeader 클래스의 초기화자에서는 SoptMember 클래스의 초기화자를 호출해야 하는데, init(alias: String)부분에서 호출하지 않았기 때문에 문제가 발생하는 것입니다.
class SoptMember {
var name: String
var part: String
init(name: String, part: String) {
self.name = name
self.part = part
}
}
class partLeader: SoptMember {
var alias: String
init(alias: String, name: String, part: String) {
self.alias = alias
super.init(name: name, part: part)
}
}
이렇게 바꿔주면! 오류가 발생하지 않습니다.

2-3) 속성 자체를 조금 더 공부해봅시다
스위프트에서 클래스의 상속 구조를 다룰 때, 자식 클래스는 부모 클래스의 모든 속성에 대해 초기화를 보장해야 합니다. 즉, 자식 클래스를 생성하면 부모 클래스의 모든 속성들이 적절한 초기값을 갖도록 해야 합니다.
위의 말대로 코드를 살펴보자면!
자식 클래스의 초기화 메소드(init) 내에서 부모 클래스의 초기화 메소드(super.init)를 호출하면, . super.init을 호출함으로써 부모 클래스의 속성들이 적절하게 초기화될 수 있겠죠??
기존 코드에선

"'super.init' isn't called on all paths before returning from initializer”이라는 에러 메시지가 나오는 것을 확인할 수 있는데요! 바로 위에서 배운 것처럼! init(alias: String) 메소드에서 super.init 호출이 누락되어있기 때문입니다.

partLeader의 초기화 메소드에서 SoptMember의 초기화 메소드를 호출하는 방식으로 코드를 수정하면 완성!
3. 편의 생성자의 관점에서
3-1) 편의 생성자는 무엇일까요? 아래 코드의 에러가 나는 부분을 바꿔주세요!
class SoptMember {
var name: String
var part: String
init(name: String, part: String) {
self.name = name
self.part = part
}
convenience init(name: String) {
self.name = "동현"
convenience init(part: String) {
self.init(name: "김동현", part: "뭘까용")
self.part = "iOS" // 'self' used before 'self.init' call or assignment to 'self'
}
}
편의생성자란!
클래스의 복잡한 초기화 과정을 단순화하는 역할을 합니다
클래스의 기본 생성자를 보다 간편하게 호출할 수 있도록 돕는 역할을 해요.
클래스의 기본 생성자를 내부에서 호출함으로써 인스턴스를 초기화 합니다.
어떻게 사용하냐면!
편의 생성자를 사용할 때는 convenience 키워드를 사용합니다.
또한❗️ 편의 생성자 내부에서는 반드시 동일 클래스의 다른 생성자를 호출(self.init)해야 합니다.
위의 코드에서는 convenience init(part: String) 생성자에서 self.init 호출이 누락되어 있어서 에러가 발생했습니다. 또한 convenience init(name: String) 생성자에서는 self.init 호출이 누락되었으며 self.name에 대한 직접적인 할당이 이루어졌습니다.
class SoptMember {
var name: String
var part: String
init(name: String, part: String) {
self.name = name
self.part = part
}
convenience init(name: String) {
self.init(name: name, part: "iOS")
}
convenience init(part: String) {
self.init(name: "김동현", part: part)
}
}
convenience init(name: String)과 convenience init(part: String)는 각각
self.name과 self.part에 값을 할당하기 전에 self.init을 호출해야 합니다.
4. Initializer Delegation 이란?
4-1) Initializer Delegation이 왜 필요할까요? 구조체와 클래스의 Initializer Delegation에 비교하고 공부해보세요!
Initializer Delegation이란,
생성자에서 또 다른 생성자를 호출하여 초기화 코드의 중복을 최대한 제거하고, 모든 프로퍼티를 효율적으로 초기화 하기위해 사용하는 것입니다. 값 형식(구조체)와 참조(클래스) 형식이 다릅니다
구조체에서의 Initializer Delegation
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = yPos
}
init(pos: Int) {
x = pos
y = pos
}
}
위의 코드에서는 두개 Initializer가 각각 코드를 초기화하는 것을 알 수 있습니다!
프로퍼티 x와 y의 값을 초기화 하는 코드가 2번 정의되어있기 때문에 중복된 코드인데요.
만약 y의 값을 23으로 초기화해야한다고 가정하면,
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = 23
}
init(pos: Int) {
x = pos
y = 23
}
}
y에 접근해서 직접 값을 바꿔줘야하는.. 일을 2번이나 해야합니다.
이는 Initializer Delegation을 따르고 있지 않는 것인데요!!
생성자에서 "또 다른 생성자"를 호출하여 초기화 코드의 중복을 최대한 제거하는 Initializer Delegation을 통해
문제를 해결할 수 있습니다.
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = yPos
}
init(pos: Int) {
self.init(xPos: pos, yPos: pos)
}
}
모든 프로퍼티를 초기화 하는 Initializer를 먼저 하나 만들고, 다른 Initializer가 이 Initializer를 사용하게 만드는 것입니다.
모든 프로퍼티를 초기화하는 Initializer는 init(xPos: Int, yPos: Int) 이 부분입니다.
struct Position {
var x: Int
var y: Int
init(xPos: Int, yPos: Int) {
x = xPos
y = 23
}
init(pos: Int) {
self.init(xPos: pos, yPos: pos)
}
}
그러면! 이렇게 하나만 바꿔줘도 모든 부분에 적용되는 간편함을 누릴 수 있습니다.

이해가 좀 쉽게 바로 안되서… 한번 그려봤더니 이해가 바로 가더라구요..
'iOS > UIKit' 카테고리의 다른 글
[iOS] Floating Button의 모든것 + StackView (3) | 2023.06.29 |
---|---|
[iOS] Image contentmode의 모든 것 (2) | 2023.06.25 |
[iOS] CollectionView의 모든 것 (5) | 2023.06.13 |
[iOS] Toggle() 을 활용한 버튼 클릭 이벤트 (0) | 2023.06.08 |
[iOS] TableView의 모든 것 (4) | 2023.06.06 |