iOS/Swift

Swift) 초기화(Initializers) 이해하기 (1/6) - 구조체(Struct)의 초기화

소들이 2022. 8. 2. 19:50

 

 

안녕하세요, 소들입니다! :)

제 포스팅의 시작은 Swift / iOS 기초를 깊게 다지자!! 였는데,

아직 다루고 싶은 글이 너무 많은데 언제부턴가 기초에 대해서는 안 썼더라구요 T.T

 

그래서  다시 Syntax 관련 포스팅 좀 하려고 합니당!!

뭐 초기화, 프로토콜, 에러 핸들링 등등이 있겠네요!

기초라 생각 하겠지만, 생각보다 우리는 잘 모르고 쓰는 기초의 내용이 많답니다!! :>

 

먼저는 크게 구조체 / 클래스 초기화에 대해 나눠서 공부할 거예요!

둘은 초기화에 대한 방식이 조금씩 다르거든요!

그리고 나서 failable / required 초기화에 대해 다뤄보겠습니당

 

제 포스팅을 iOS를 막 시작하신 초보분들이 많이 보시는 만큼

쉽게 풀어쓰려 노력 해보겠습니다 :)

모든 포스팅은 편의 말투로 합니다~!!

 

 

 

 

1. 초기화(Initializers)란?

 

구조체 / 열거형 / 클래스의 인스턴스를 생성하는 것이 초기화로,

초기화의 역할은 모든 프로퍼티를 기본값으로 초기화 하는 것이다

인스턴스 내 기본값이 존재하지 않는 프로퍼티가 있을 경우, 초기화에 실패하고 인스턴스는 생성되지 않는다

 

일단 초기화의 정의부터 보자면 위와 같음!!

가장 중요한 말은 "모든 프로퍼티를 기본 값으로 초기화" 한다는 것임

따라서 만약, 초기화가 끝나는 시점에 모든 프로퍼티가 기본 값을 가지고 있지 않다면

그것은 초기화 실패로, 즉 인스턴스가 생성되지 않음

 

엥? 초기화도 실패가 될 수 있나여? 라고 생각 하겠지만,

이는 다다음쯤에 공부할 failable 초기화에 대한 얘기니 이건 잠시 접어두고

기본적으로 우리가 선언할 초기화는 failable이 아니기 때문에 초기화가 절대 실패해선 안됨

고로, 초기화 될 때 모든 프로퍼티는 기본 값으로 초기화가 되어 있어야 함

 

이 말을 염두에 두고 구조체(Struct)의 초기화에 대해 보러가자~~!!

 

 

 

 

2. 구조체(Struct)를 초기화 하는 방법

 

2-1. 선언과 동시에 프로퍼티에 기본값 넣어주기

 

struct Human {
    let name: String = "Sodeul"
    let age: Int28
}

 

 

위처럼 name이란 프로퍼티에 Sodeul이란 값을,

age란 값엔 28이란 값을 직접 선언과 동시에 기본값을 넣어주며 초기화를 해줘버리는 것임

 

이렇게 선언과 동시에 기본값으로 초기화를 시킬 수 있음!!!

 

 

 

2-2. 프로퍼티의 타입을 옵셔널(Optional) 타입으로 설정하기

 

struct Human {
    let name: String?
    let age: Int?
}

 

 

위처럼 name, age 값을 옵셔널 타입으로 설정하는 것 또한

초기화를 진행해준 것임

 

따라서 만약 값이 있을지도 없을지도 모를 경우에는 위처럼 옵셔널 타입으로 설정하면 됨

그러면 초기화를 할때 옵셔널 타입인 name과 age는 자동으로 nil로 초기화가 됨

 

 

 

2-3. init 함수에서 값을 설정해주기

 

struct Human {
    let name: String
    let age: Int
    
    init(name: String) {
        self.name = name
        self.age = 28
    }
}
 

 

 

초기화를 진행 하는 함수를 바로 init 함수라고 함!!!(앞으론 생성자라고도 부름)

따라서 위에서처럼 프로퍼티를 선언할 때 어떤 값을 지정해줄 수도 있지만,

생성자 안에서 해당 프로퍼티들을 초기화 시킬 수도 있음

 

중요한 건 뭐라구!?!?

init(초기화) 함수가 종료되기 전까진 모든 프로퍼티가 기본 값을 가져야 한다!!!

뭐 위처럼  init 함수에서 파라미터를 직접 받아서 초기화를 진행할 수 있음

 

 

 

 

3. 저는 지금껏 구조체 만들 때 기본값 초기화도 안 하고, init 함수 안 만들고 썼는데요?

 

자, 이정도 읽었다면

구조체를 어느정도 좀 칠 줄 아는 분이라면 위 같이 의문이 들어야 정상임

분명 처음에 초기화는 모든 프로퍼티가 값을 가져야먄 초기화에 성공해서 인스턴스를 생성한다고 했는데!!

 

 

struct HumanStruct {
    let name: String
    let age: Int
}
 
class HumanClass {              // Class 'HumanClass' has no initializers
    let name: String
    let age: Int
}
 

 

 

분명 똑같이 프로퍼티 name, age를 초기화 하지 않은 구조체클래스를 만들었을 경우,

클래스만 initializers를 갖지 않는다면서 에러가 남

(클래스는 다음 포스팅에서 공부할 거지만 대충 에러 난다고만 보셈)

 

앞서 공부할 때 머라고 했음!?

초기값을 갖기 위해선 선언과 동시에 기본값을 갖거나, 혹은 옵셔널 타입으로 선언되어야 한다고 했음

근데 위에서 name, age는 기본 값을 갖거나 옵셔널 타입도 아니네!?!?

따라서 이 경우에는 init 함수를 직접 작성해서, 해당 함수 안에서라도 반드시 프로퍼티 초기화를 지정했어야 했음

근데 그 과정마저 없으니, 당연히 클래스처럼 너 초기화(initializers) 없는데요? 하고 에러가 나는 것이 정상

 

근데 왜 클래스만 에러가 날까? 구조체는 왜 안 나!?

이에 대한 답은,

 

구조체는 Memberwise Initializers 라는 초기화를 기본으로 제공하기 때문임

 

 

 

 

4. 구조체(Struct)에서만 제공하는 Memberwise Initializers

 

구조체가 자동으로 제공하는 생성자로,

파라미터를 통해 모든 프로퍼티의 초기화를 가능하게 한다

 

이게 무슨 말이냐면,

구조체의 경우에는 프로퍼티의 초기화를 따로 지정하지 않을 경우,

초기화되지 않은 프로퍼티를 초기화할 수 있는 init 함수를 자동으로 제공해버리는 것임

 

 

 

 

이런 식으로!! 

인스턴스를 생성하기 위해 init을 할 때 무조건 name과 age를 설정할 수 있도록 

위처럼 프로퍼티를 모두 초기화할 수 있는 Initializers를 자동으로 제공하는 것임!!

 

이해 가져!?!?

근데 이 memberwise initializers에 몇 가지 성질이 있는데 이에 대해 공부해보겠음 :)

 

 

 

4-1. memberwise initializers로 제공되는 생성자의 파라미터는, 프로퍼티가 선언된 순서와 갯수에 맞추어 생성된다. 이때 파라미터의 이름은 프로퍼티의 이름으로 설정된다

 

struct Human {
    let name: String
    let age: Int
}

 

Human.init(name: "Sodeul", age: 28)
Human.init(age: 28, name: "Sodeul")          //error!!!
 

 

 

초기화되지 않은 프로퍼티의 갯수가 2개(name, age)이니 파라미터로 2개의 값을 받는 것이고, 

내가 프로퍼티를 선언할 당시 순서가 name을 먼저 선언하고, 그 뒤에 age를 선언했기 때문에

이때 생성자의 파라미터는 프로퍼티의 선언된 "순서"를 지키기 때문에 

age를 name 보다 먼저 파라미터로 가져선 안 됨


또한 내가 name, age란 이름으로 프로퍼티를 선언했기 때문에

파라미터 이름 또한 name, age로 생성

 

 

 

4-2. 프로퍼티가 let으로 선언 되어 있고, 이미 초기화가 되어있는 상태면 생성자 목록에서 제외된다

 

struct Human {
    let name: String = "Sodeul"
    let age: Int
}
 
Human.init(age: 28)

 

 

만약 위처럼 name이란 프로퍼티가 let으로 선언되어있고,

초기값을 가지고 있는 경우에는 생성자의 파라미터에서 제외

 

왜냐??

let으로 선언될 경우, 이미 초기값을 가진경우 값을 수정할 수 없기 때문

어차피 memberwise initializers를 통해 파라미터로  값을 받아도 name이란 프로퍼티의 값을 변경할 수 없기 때문임!!

 

 

 

4-3. 프로퍼티가 var로 선언 되어 있고, 이미 초기화가 되어있는 상태면 해당 프로퍼티를 초기화 하는 생성자와, 제외된 생성자 두 개를 제공한다

 

 

struct Human {
    var name: String = "Sodeul"
}
    
Human.init()                    // name이 제외된 생성자
Human.init(name: "Sodeul")      // name을 초기화 시킬 수 있는 생성자

 

 

name이란 프로퍼티 하나로 예를 보자면,

name이란 프로퍼티가 var로 선언되어 있기 때문에

초기값을 갖더라도 이니셜라이저를 통해 값을 변경할 수 있어서

이때는, name이란 프로퍼티를 초기화 할 수 있는 생성자 한 개와 초기화 하지 않아도 되는 생성자 한개가 각각 제공됨!

 

 

struct Human {
    var name: String = "Sodeul"
    var age: Int
    var alias: String = "Deulso"
}
    
Human.init(age: 28)
Human.init(name: "Sodeul", age: 28)
Human.init(age: 28, alias: "Deulso")
Human.init(name: "Sodeul", age: 28, alias: "Deulso")
 

 

 

만약 위와 같이 프로퍼티를 선언할 경우,

저렇게 다양한 memberwise initializers가 제공 된답니당 :)

 

 

 

4-4. 🚨중요🚨 생성자를 "직접" 생성한 경우, memberwise initializers는 더이상 제공되지 않는다

 

 

 

위처럼 내가 구초제 안에 init 함수를 직접 선언해줄 경우,

이땐 더이상 memberwise initializers는 제공되지 않음!!! 중요함!!!

 

memberwise initializers는 구조체 안에 init 함수를 직접 구현하지 않은 경우에만 제공됨

 

그럼 나는 만약 직접 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: "Sodeul")
Human.init(name: "Sodeul", alias: "Deulso")

 

 

이렇게 직접 구현한 init 함수를 extension으로 뺄 경우,

이때는 내가 정의한 init과 memberwise initializers 모두 사용할 수 있음 :)

 

 

 

4-5. 🚨중요🚨 프로퍼티 중 하나라도 private일 경우, memberwise initializers의 접근제어자 또한 private이다

 

 

 

이처럼 선언된 프로퍼티 중 하나라도 private 접근 제어자를 가질 경우,

해당 memberwise initializers 또한 접근 제어자가 private이 됨!!

따라서 내부에서만 사용할 수 있고, 외부에서 더이상 사용할 수 없음

 

 

 

 

 

 

.

.

.

 

구조체에서의 초기화 포스팅 끝!!

memberwise initializers에 대해 잘 알아둡시당 :)

오타, 잘못된 내용, 궁금증 있다면 댓글 남겨주세요~