본문 바로가기

iOS (스파르타)

Property 정리하기 - 1 (저장 프로퍼티, 지연 저장 프로퍼티)

지금까지 클래스, 구조체에서 선언하던 상수/변수가 프로퍼티라고 알고있고, 초반에 프로퍼티에 대해 글 작성했을 때

저장 프로퍼티와 연산 프로퍼티에 대해서만 작성했었다.

 

근데 Swift에서는 프로퍼티가 총 세가지 형태로 존재한다.

 

이 글의 모든 내용은 소들이님의 블로그를 표방했다,, 내 창작물 아님 ㅎㅋ

내가 내 블로그에 작성해두고 보려고 쓴거임 ㅠ

 

Stored Property: 저장 프로퍼티

Computed Property: 연산 프로퍼티

Type Property: 타입 프로퍼티

 

그 중 이번에는 Stored Property 저장 프로퍼티에 대해서 알아보자

 

Swift 공식 문서를 보면 프로퍼티라는 것이 값을 Class, Struct, Enum과 연결한다고 하는데,

어떤 프로퍼티는 위 세가지 중에 어떤 때에 사용할 수 있는지에 대해서도 볼것이다.

 

 

저장 프로퍼티 (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란 변수 모두 저장 프로퍼티이다.

 

여기서 저장 프로퍼티의 클래스와 구조체의 차이점에 대해 알아볼거다.

 

클래스 인스턴스를 let/var 로 선언한다는 것은

만약 jenna란 옵셔널 "상수"로 Human 클래스 인스턴스를 만들었다

let jenna: Human? = .init()

이때 선언 위치는 지역변수라고 가정하고

이 jenna란 인스턴스를 통해 저장 프로퍼티인 name과 age를 각각 변경 해볼거다.

name이란 값 변경은 에러가 나고, age란 값 변경은 문제가 없다.

 

name은 상수니 초기화 이후로 변경을 못해서 에러가 나고, age는 변수니 초기화 이후에 변경이 가능하니 당연한거 아닌가 하는데

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

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

 

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

 

 

구려

 

클래스는 참조타입이기 때문에, 메모리에 이렇게 저장이 된다.

지역 상수 jenna는 스택에 할당 되고, 실제 Human 인스턴스는 힙에 할당 된다.

 

스택에 있는 jenna는 힙 영역에 있는 인스턴스를 참조하고 있는 형태이다.

따라서 jenna 안에는 힙에 할당된 인스턴스의 주소값이 들어가 있다.

 

근데 jenna란 클래스 인스턴스를 생성할 때 let으로 한다는 것은

실제 힙 영역에 저장된 저장 프로퍼티 name, age와는 상관없이 스택영역에 저장된 jenna안의 주소값이 상수로 설정되는 것

 

따라서 클래스의 경우, 인스턴스 생성 당시 let으로 선언하든 var로 선언하든 클래스의 저장 프로퍼티에 접근하는 것에는 아무런 영향을 주지 않는다.

 

그렇다면 무엇에 영향을 줄까?

jenna란 스택상수는 인스턴스의 값을 가지고 있고 이제 이 jenna란 상수 안의 값을 변경하는 것에 영향을 준다.

 

상수니까 값이 변경될 수 없으니 jenna란 상수가 옵셔널 타입이지만 nil을 할당할 수도 없고

jenna란 상수에 다른 Human Instance를 대입할 수도 없다.(다른 인스턴스의 주소값을 가질수없다)

당연히 클래스 인스턴스를 생성할 때 var로 한다면

var jenna: Human? = .init()

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

다른 Human Instance를 대입할 수도 있다.(name은 상수니 인스턴스 선언과 별개로 변경 불가능하다.)

 

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

 

 

구조체 인스턴스를 let/var로 선언하는 것은

우리가 만약 jenna란 옵셔널 "상수"로 Person 구조체 인스턴스를 만들었다.(Swift에서는 클래스, 구조체 모두 인스턴스라 한다.)

 

let jenna: Person? = .init()

위와 같이

그리고 나서 이 jenna란 인스턴스를 통해 저장 프로퍼티인 name과 age를 각각 변경 해볼것이다.

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

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

구조체는 값타입이기 때문에, 메모리 저장은 스택 영역에 저장된다.

구조체의 저장 프로퍼티들도 모두 stack에 같이 올라간다는 것(클래스처럼 힙에 할당된 인스턴스를 참조하는 것이 아니기 때문에)

 

따라서 jenna란 구조체 인스턴스를 생성할 때 let으로 한다는 것은

name이 상수 저장 프로퍼티고, age가 변수 프로퍼티고 상관없이

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

당연히 nil을 할당하는 것도 안된다.

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

 

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

var jenna: Person? = .init()

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

또 var로 선언된 저장 프로퍼티의 값도 변경할 수 있다.(name은 상수니 인스턴스 선언과 별개로 변경 불가능)

 

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

 

 

지연 저장 프로퍼티 (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 jenna: Human = .init()

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

초기화 구문(initializer)이 불리는 순간 모든 프로퍼티가 초기화 되어야 함(기본값을 가지든, 생성자를 통해 초기화 하든)

따라서 jenna란 인스턴스를 만들고 init을 호출한 순간,

Human 인스턴스 내에 있는 모든 프로퍼티들이 초기화 되며, contacts라는 프로퍼티도 초기화 된다.

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

이게 정상이다

근데 만약 contact란 저장 프로퍼티 앞에 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 jenna: Human = .init()

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

그러면 Contacts Init 🐙 이란 문구가 더이상 나타나지 않는다.

lazy 즉 지연이 됐기 때문에

선언만 됐을 뿐 contacts란 변수 자체가 초기화 되지 않은 것이다.

이제 contacts란 변수에 처음으로 접근하고자 하면

jenna.contacts.address = "none"

이때 contacts란 변수가 초기화 되면서

문구가 나오게 된다.

 

 

 

지연 저장 프로퍼티의 특징

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

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

let 으로 선언될 경우

우리가 필요한 시점에 초기화를 진행할 수 없기 때문이다.(이미 메모리에 올라가있지만, 필요한 시점에 원하는 값으로 초기화 되어야 해서)

 

lazy를 잘 사용할 경우 성능도 향상되고, 메모리 낭비도 줄일 수 있다.

 

열거형에서는 못 쓸까,,?

넴 못씁니다.