본문 바로가기

iOS/Swift

Swift) 프로퍼티 정복하기 (1/4) - 저장 프로퍼티(Stored Property)

 

 

 

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

해이해진 마음을 다잡고!! 다시 정복하기 시리즈?로 돌아왔습니다!

사실 이 정복하기 포스팅은 저를 위해 쓰기 시작한 거였어요.

아무리 쉽건 어렵건 한번 보고 넘어가는 것은 제 성격에 안 맞아서,

확실하게 정리하고 넘어가야 제 스스로에게 도움이 되거든요!!

 

근데, 제 Swift 정복하기 포스팅이 도움이 많이 되었단 분들이 꽤 있으시더라구요..헿

제 스스로를 위해서, 그리고 이제는 제 포스팅으로 공부할 iOS 초보 개발자분들을 위해

다시 열심히 해보려고 해요!!! :)

 

뭐이렇게 주절주절하냐 싶지만,

요즘 슬럼프 아닌 슬럼프가 와서 -.-;;;;;; 이쯤되면 슬럼프랑 절친;;

무려 4파트로 나눠놨지만 더 늘어날 수도 있고, 줄어들 수도 있으니!!!

 

시작해봅니다!!!

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

 

 

 

 

1. 프로퍼티란?

 

우리가 지금껏 클래스, 구조체에서 선언하던 상수/변수가 프로퍼티 아닌가요!?!?!

 

처음 위같은 생각을 먼저 했었는데, 뭐 맞긴 맞음!!!

근데 Swift에선 이 프로퍼티가 총 3가지 형태로 존재

 

Stored Property : 저장 프로퍼티

Computed Property : 연산 프로퍼티

Type Property : 타입 프로퍼티

 

이중에 우리가 오늘 공부할 것이 바로 Stored Property, 즉 저장 프로퍼티임!!

이 다음 포스팅은 연산 프로퍼티, 그 다음은 타입 프로퍼티 예정이니 스킵~!!

 

그리고 Swift 공식 문서를 보면

이 프로퍼티라는 것이 값을 Class, Struct, Enum과 연결한다고 하는데,

어떤 프로퍼티는 위 세가지 중에 어떤 때에 사용할 수 있는지에 대해서도 보겠음 :)

 

 

 

 

2. 저장 프로퍼티(Stored Property)란?

 

클래스와 구조체에서만 사용할 수 있고, 값을 저장하기 위해 선언되는 상수/변수

 

ㅇㅖ... 먼저 우리가 지금껏 아무렇지 않게 클래스, 구조체에서

변수와 상수를 담기 위해 사용하는 다음과 같은

 

 

class Human {
    let name: String = "unknown"
    var age: Int = 0
}
 
struct Person {
    let name: String = "unknown"
    var age: Int = 0
}
 

 

 

Human이란 클래스와 Person이란 구조체에 저장된

name이란 상수, age란 변수 모두 저장 프로퍼티임!!

 

자, 근데 여기서 저장 프로퍼티의 클래스와 구조체의 차이점?에 대해 알아볼 것임!

이것은 Class vs Struct 편을 따로 쓸 거라 거기서 쓸까 고민 했는데,

그냥 저장 프로퍼티 아는 김에 알아버리자

 

 

 

2-1. 클래스 인스턴스를 let/var으로 선언한다는 것은

 

자, 우리가 만약 sodeul이란 옵셔널 "상수"로 Human 클래스 인스턴스를 만들었음

 

 

let sodeul: Human? = .init()

 

 

위와 같이!!!!! 이때 선언 위치는, 지역변수라고 가정 하겠음!!!

그러고 나서 내가 이 sodeul이란 인스턴스를 통해

저장 프로퍼티인 name과 age를 각각 변경 해보겠음!!!

 

 

 

 

자, 그럼 name이란 값 변경은 에러가 나고, age란 값 변경은 문제가 없음!

 

엥? name은 상수니 초기화 이후로 변경을 못해서 에러가 나고,

age는 변수니 초기화 이후에 변경이 가능하니 당연한 거 아니예여??

 

라고 하겠져?

그럼 나는 이렇게 의문을 던질 것임

 

sodeul이란 인스턴스를 let, 즉 "상수"로 선언 했는데!!

어떻게 age란 저장 프로퍼티가 var라 해도 변경할 수 있을까요!!??

 

메모리에 대해 좀 아시는 분이라면 왜 저런 질문을 던지나 의아할테고,

모르시는 분이면 혼란이 올 수 있음!!! 소들이 메모리 기초를 먼저 보고오면 좋겠지만,

일단 sodeul이란 클래스 인스턴스를 할당할 경우

 

 

 

 

클래스참조타입이기 때문에, 메모리에 이렇게 저장됨!!!!

 

지역 상수 sodeul은 스택에 할당 되고

실제 Human 인스턴스는 힙에 할당 됨

 

스택에 있는 sodeul은 힙 영역에 있는 인스턴스를 참조하고 있는 형태임

따라서 sodeul 안엔 힙에 할당된 인스턴스의 주소값이 들어가 있음

 

자 근데, 내가 sodeul이란 클래스 인스턴스를 생성할 때 let으로 한단 것

 

 

 

 

실제 힙 영역에 저장된 저장 프로퍼티 name, age와는 상관 없이

스택 영역에 저장된 sodeul 안의 주소값이 상수로 설정되는 것임!!!!

상수니까 값을 바꿀 수 없어서 자물쇠 건 것으로 표현해 봤음!

 

따라서,

클래스의 경우, 인스턴스 생성 당시 let으로 선언하든 var로 선언하든

클래스의 저장 프로퍼티에 접근하는 것엔 아무 영향을 주지 않음

 

그럼 무엇에 영향을 줄까?

sodeul이란 스택 상수는 0x11111111이란 인스턴스의 값을 가지고 있음

그럼 이제 이 sodeul이란 상수 안의 값을 변경하는 것에 영향을 줌!

 

상수니까 값이 변경될 수 없으니

sodeul이란 상수가 옵셔널 타입이지만 nil을 할당할 수도 없고,

 

 

 

 

sodeul이란 상수에 다른 Human Instance를 대입할 수도 없음

(다른 인스턴스의 주소값을 가질 수 없으니!!)

 

 

 

 

아, 당연히 다음과 같이 클래스 인스턴스를 생성할 때 var로 한다면

 

 

var sodeul: Human? = .init()

 

 

(옵셔널 타입이면) nil도 할당 가능하고,

 

 

 

 

다른 Human Instance를 대입할 수도 있음!!!

(name은 상수니 인스턴스 선언과 별개로 변경 불가능함~.~)

 

이것이 Class에서 인스턴스를 let 혹은 var로 선언하는 것의 차이임!!!

 

 

 

2-2. 구조체 인스턴스를 let/var으로 선언한다는 것은

 

자, 우리가 만약 sodeul이란 옵셔널 "상수"로 Person 구조체 인스턴스를 만들었음

(아 Swift에선 클래스, 구조체 모두 인스턴스라 합니다!!:))

 

 

let sodeul: Person? = .init()

 

 

위와 같이!!!!!

그러고 나서 내가 이 sodeul이란 인스턴스를 통해

저장 프로퍼티인 name과 age를 각각 변경 해보겠음!!!

 

 

 

 

자, 이번엔 name이란 상수도, age란 변수도 모두 값 변경을 하지 못한다며 에러가 발생

 

엥? name은 상수니 초기화 이후로 변경을 못해서 에러가 나고,

age는 변수니 초기화 이후에 변경할 수 있는데 왜 에러가 나나염??????

 

라고 의문을 품을 수 있음!! (안 품는다면 메모리 이해도가 높으시군요!)

일단 sodeul이란 구조체 인스턴스를 할당할 경우

 

 

 

 

구조체값타입이기 때문에, 메모리에 이렇게 저장됨!!!!

구조체의 저장 프로퍼티들도 모두 stack에 같이 올라간다는 것임

(클래스처럼 힙에 할당된 인스턴스를 참조하는 것이 아니기 때문에)

 

따라서, sodeul이란 구조체 인스턴스를 생성할 때 let으로 한단 것

 

 

 

 

name이 상수 저장 프로퍼티고, age가 변수 프로퍼티고 나발이고

구조체 인스턴스를 let으로 선언한 순간 구조체의 모~든 멤버를 변경할 수 없는 것

 

 

 

 

당연히 nil을 할당하는 것도 안 되겠지!! :)

다른 구조체 인스턴스를 할당받는 것 또한 안 되고!!!

 

당연히 구조체 인스턴스 또한 var로 선언하면

 

 

var sodeul: Person? = .init()

 

 

 

nil도 할당받을 수 있고, 다른 구조체 인스턴스를 할당받을 수 있고

 

 

 

 

또 var로 선언된 저장 프로퍼티의 값도 변경할 수 있음 :)

(name은 상수니 인스턴스 선언과 별개로 변경 불가능함~.~)

 

이것이 구조체에서 인스턴스를 let 혹은 var로 선언하는 것의 차이임!!!

이해가 됐기를!!!

 

 

 

 

3. 지연 저장 프로퍼티(Lazy Stored Property)

 

프로퍼티가 호출되기 전까지는 선언만 될 뿐 초기화되지 않고 있다가,

프로퍼티가 호출되는 순간에 초기화 되는 저장 프로퍼티

 

호엥.. 먼말이냐고여?

만약 다음과 같은 Contacts를 저장 프로퍼티로 가지는 Human이란 클래스가 있음

 

 

class Contacts {
   var email: String = ""
   var address: String = ""
 
    init() { print("Contacts Init 🐙") }
}
 
class Human {
   var name: String = "unknown"
   var contacts: Contacts = .init()
}
 

 

 

자, 이제 내가 Human이란 클래스 인스턴스를 만들고 초기화 했음

 

 

let sodeul: Human = .init()

 

 

여러분, 원래 클래스건 구조체건 인스턴스를 생성할 경우

초기화 구문(initializer)이 불리는 순간 모~~든 프로퍼티가 초기화 되어야 함!!!!

(기본값을 가지든, 생성자를 통해 초기화 하든!!!)

 

따라서 우리가 sodeul이란 인스턴스를 만들고 init을 호출한 순간,

Human 인스턴스 내에 있는 모~~든 프로퍼티들이 초기화 되며, contacts란 프로퍼티도 초기화 됨!!

따라서 다음과 같이 Contacts 클래스의 초기화 구문이 실행됨

 

 

 

 

이게 정상임!!!

근데 만약 내가 contacts란 저장 프로퍼티 앞에 lazy란 키워드를 붙였음

 

 

class Contacts {
   var email: String = ""
   var address: String = ""
 
    init() { print("Contacts Init 🐙") }
}
 
class Human {
   var name: String = "unknown"
   lazy var contacts: Contacts = .init()
}
 

 

 

이렇게!!! 그러고 다시 

 

 

let sodeul: Human = .init()

 

 

이렇게 인스턴스를 생성하고 초기화 했음

그러면 말이지, 저 Contacts Init 🐙이란 오징어 대가리가 더이상 나타나지 않음

 

왜냐?? lazy! 지연되거든!

선언만 됐을 뿐 contacts란 변수 자체가 초기화 되지 않는 것임

 

이제, 다음과 같이 내가 contacts란 변수에 처음으로 접근하고자 하면

 

 

sodeul.contacts.address = "none"

 

 

그럼 이제, 이때 contacts란 변수가 초기화 되면서,

 

 

 

 

문어 대가리가 등장하는 것임!!!!!! 오케이!?????

 

 

 

3-1. 지연 저장 프로퍼티의 특징

 

 인스턴스가 초기화와 상관 없이, 처음 사용될 때 개별적으로  초기화 된다 

 

이건 위에서 봤으니 패쓰

 

 따라서 항상 "변수"로 선언 되어야 한다 

 

 

 

 

머선 말이냐면, let으로 선언될 경우

우리가 필요한 시점에 초기화를 진행할 수 없기 때문임

(이미 메모리에 올라는 가 있지만, 필요한 시점에 내가 원하는 값으로 초기화 되어야 하니)

 

 

 

3-2. 지연 저장 프로퍼티는 어떨 때 쓸까?

 

자, 만약 내가 미쳐갖고 Contacts란 클래스에 100,000개의 Element를 갖는 

email이란 저장 프로퍼티를 선언 했음!

 

 

class Contacts {
    var email: [String= .init(repeating: "", count: 100000)
    var address: String = ""
 
    init() { print("Contacts Init 🐙") }
}
 
class Human {
    let name: String = "unknown"
    lazy var contacts: Contacts = .init()
}
 

 

 

근데 Contacts에 대한 정보가 있는 유저도 있고, 없는 유저도 있을 거 아님???

 

만약 5,000명의 유저가 Contacts 정보를 제공하고,

5,000명의 유저가 Contacts 정보를 제공하지 않는다고 생각해보셈

 

lazy로 선언하지 않는다면, Contacts란 인스턴스가

유저 수만큼 10,000개 생겨 버림

(Human 인스턴스가 초기화 됨과 동시에 생성되니)  

 

근데, lazy로 선언할 경우 Contacts란 인스턴스가

contacts란 프로퍼티에 접근하는 유저 수인 5,000개 만큼만 생기고,

접근하지 않는 유저 수인 5,000개는 초기화 되지 않아 생기지 않을 것임!!

 

따라서,

이럴 경우 lazy를 사용하면 메모리가 반으로 절약된다는 말!

예제가 좀 띠용스럽긴 한데..;;쨌든 이 lazy를 잘 사용할 경우

 

성능도 향상되고, 메모리 낭비도 줄일 수 있다

 

라고 함네다..........:)

 

 

 

 

4. 열거형에선 못 쓸까?

 

 

 

 

어림도 없지

 

 

 

 

 

.

.

.

 

오늘은 프로퍼티 중에서도 저장 프로퍼티에 대해서 알아봤어요 :)

사람인지라 틀린 내용이 있다면 언제든 피드백 주시구!!!!

궁금한 점 또한 댓글 주시면 감사하겠습니다!!

 

클래스 인스턴스와 구조체 인스턴스를 let/var로 선언할 경우 어떤 점이 다른지에 대해

제가 실제 면접 질문으로 받았던 적이 있어요!!

그러니 꼭 완벽히 숙지하시길!!!!

 

그럼 20,000

 



Calendar
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
최근 댓글
Visits
Today
Yesterday