안녕하세요 Niro 입니다!

iOS, UIKit, SwiftUI 를 공부하는 것도 중요하지만 개발하면 할 수록 느끼는 것이 Swift 라는 언어를 더 깊게 파야겠다는 생각이 들었습니다.

여러 블로그들을 참고하여 공부하는 것도 좋지만 Swift 공식문서인 The Swift Programming Language 를 통해 공부하는 것이 오래걸리지만 스스로 공부하고 내것으로 만들기 가장 쉬워 참고해보았습니다!

자, 이번에 살펴볼 내용은 Swift 의 Property 입니다!
Property 라는 단어는 많이 봤을 단어인데 정의 내리기가 너무나 어려운 단어이기도 합니다.

Swift 에는 여러가지 Property 가 존재하는데 그 중 Store Property ( 저장 프로퍼티 ) 를 살펴보려고 합니다!

먼저 Property 가 뭔지 알아볼까요?



1️⃣ Property ( 프로퍼티 ) 란?

우리는 알게 모르게 아주 많이 Property 를 사용해왔습니다.
어디서 사용 했냐구요?

바로 Class ( 클래스 ), Struct ( 구조체 ), Enum ( 열거형 ) 에서 사용한 변수나 상수에 관련한 값입니다!

그냥 단순히 값을 저장하는 용도로 사용을 해왔었는데 놀랍게도 Property 는 3가지 형태로 존재한다고 합니다!

  • Stored Property ( 저장 프로퍼티 )
  • Computed Property ( 연산 프로퍼티 )
  • Type Property ( 타입 프로퍼티 )

저희는 이 3가지 중에서 첫번째로 Stored Property ( 저장 프로퍼티 ) 를 알아보고자 합니다!



2️⃣ 저장 프로퍼티 ( Stored Properties )

이름 그대로 저장 프로퍼티는 값을 저장하고 있는 프로퍼티 입니다.
우리가 값을 저장하기 위해 변수를 만드는 것과 같이 가장 많이 사용하는 형태로 let 키워드를 통해 상수, var 키워드를 통해 변수를 선언할 수 있습니다.


struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 범위 값은 0, 1, 2 입니다.
rangeOfThreeItems.firstValue = 6
// 범위 값은 6, 7, 8 입니다.

FixedLengthRange 라는 구조체가 있고 첫 값을 의미하는 변수 firstValue 와 길이를 나타내는 상수 length 인 저장 프로퍼티가 있습니다.

FixedLengthRange 구조체를 인스턴스화 시킨 rangeOfThreeItems 라는 변수가 생겼고 저장 프로퍼티를 각각 0 과 3으로 초기화 시킨것도 보실 수 있습니다.

마지막 줄에는 초기화 시킨 firstValue 라는 프로퍼티의 값을 6으로 바꾸기도 했습니다. 그럼 length 프로퍼티는 값을 바꿀 수 있을까요?

↔️ 구조체에서 var 와 let 의 차이

다음과 같이 오류가 발생합니다.

firstValuevar 로 선언된 저장 프로퍼티이기 때문에 값이 변경되지만 lengthlet 으로 선언된 저장 프로퍼티이기 때문에 변경할 수 없습니다.

당연한 말 아니냐구요?

그렇다면 저장 프로퍼티가 아닌 구조체 인스턴스를 var 에서 let 으로 선언하면 어떻게 될까요?

위 사진처럼 var 로 선언된 저장 프로퍼티까지 오류가 나는 것을 볼 수가 있습니다.
var 로 선언된 변수는 값을 바꿀 수 있고 let 으로 선언된 상수는 한번 초기화 시키면 값을 바꿀 수 없다고 알고 있는데 왜 이런 현상이 나타난걸까요?

바로 구조체 의 특징 때문입니다.
구조체는 값 타입 ( value Type ) 이기 때문에 인스턴스를 만들 때 Stack 에 값으로 저장이 됩니다. 당연히 구조체에 있는 모든 프로퍼티들도 같이 올라가겠죠?

즉, let 으로 인스턴스를 선언한다면 모든 내용을 바꿀 수 없는 상황이 발생합니다.

그렇다면 Class 에선 어떻게 될까요?

↔️ Class 에서 var 와 let 차이

class FixedLengthRange {
    var firstValue: Int = 0
    let length: Int = 0
}
var rangeOfThreeItems = FixedLengthRange()

rangeOfThreeItems.firstValue = 6

클래스도 구조체와 마찬가지로 firstValue 라는 변수와 length 라는 상수 둘 다 저장 프로퍼티를 의미 합니다.
또한 구조체에서 var 로 인스턴스를 선언했을 때 처럼 클래스에서도 var로 인스턴스를 선언할 경우 firestValue 는 변수이니 변경이 가능하고 length 는 상수 이니 변경이 불가능합니다.

앞서 보신 것 처럼 구조체는 let 으로 인스턴스를 선언시 var, let 으로 선언된 저장 프로퍼티 둘 다 변경이 불가능했었죠?

하지만 let 으로 클래스 인스턴스를 선언했을 때는 구조체와 다른 현상이 나타납니다.

클래스에서는 length 라는 상수 저장 프로퍼티만 오류가 발생한 것을 볼 수 있습니다.
당연히 let 으로 선언되었으니 오류가 나는거 아니냐 라는 질문을 하실 수도 있습니다.

다시 코드를 보실까요?

분명 클래스 인스턴스를 선언할때 let 으로 선언이 되어 있고 저는 여기서 궁금증이 생겼습니다.

"구조체에서는 안됐지만 let 으로 선언된 클래스 인스턴스에서 var 인 저장 프로퍼티를 왜 바꿀 수 있을까요?"

앞서 보신 것처럼 구조체는 값 타입으로 Stack 에 값으로 저장되었었죠?
클래스는 참조 타입 ( Reference Type ) 으로 인스턴스화 될 때 rangeOfThreeItems 상수는 Stack 에 할당 되고 클래스의 프로퍼티는 Heap 에 저장된다는 차이점이 있습니다.

결국 우리가 사용할 값들은 Heap 에서 불러오는 것이고 Stack 에는 Heap 에 있는 데이터가 몇 번지에 있는지 알려주는 인스턴스의 주소값이 저장되어 있습니다.

즉, let 으로 클래스 인스턴스를 선언하는 행위는 Stack 에 저장된 rangeOfThreeItems 의 주소값을 변경할 수 없게 만드는 것으로 저장 프로퍼티의 값을 바꾸는 행위와는 전혀 관련 없다는 것입니다!

그렇기 때문에 우리는 varlet 으로 클래스 인스턴스를 선언하던지 간에 var 로 선언된 저장 프로퍼티의 값을 변경할 수 있다는 것입니다.

구조체와 클래스의 차이를 설명하기 위해 StackHeap 이라는 메모리 관련 내용들이 나오게 됐는데 개발하면서 꼭 필요한 내용이니 다른 글에서 더 자세히 알아보도록 하겠습니다!



3️⃣ 지연 저장 프로퍼티 ( lazy Stored property )

자.... 아직 안끝났습니다!
또 하나의 저장 프로퍼티가 있습니다.

값이 처음으로 사용되기 전까지는 계산되지 않는 프로퍼티를 의미합니다.

즉, 선언은 되어있지만 값이 없이 있다가 해당 프로퍼티를 사용할 때 초기화가 된다는 것입니다.

또 하나의 특징으로 반드시 변수 ( var ) 로 선언을 해야 합니다!

상수 ( let ) 는 초기화 전에 항상 값을 갖고 있는 프로퍼티 여서 사용하기 전에 값이 없는 상태로 상수를 만들 수가 없기 때문입니다!

뭔가 어렵기도 하고 아리송한 느낌이지만 가장 위로가 되는 부분은 저장 프로퍼티 앞에 lazy 라는 키워드를 적으면 되는 단순한 사용법을 갖고 있습니다!

"아니 그래서 일단.. 선언하면 바로 초기화 되는 것이지 사용할 때 초기화가 된다고?"
라는 의문과 함께 예시를 볼까요?

⏯️ 지연 저장 프로퍼티 예시


class DataImporter {
    /*
        DataImporter는 외부 파일에서 데이터를 가져오는 클래스입니다.
         이 클래스는 초기화 하는데 매우 많은 시간이 소요된다고 가정하겠습니다.
     */
    var filename = "data.txt"
    // 데이터를 가져오는 기능의 구현이 이 부분에 구현돼 있다고 가정
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 데이터를 관리하는 기능이 이 부분에 구현돼 있다고 가정
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 인스턴스는 이 시점에 생성돼 있지 않습니다.

DataManager 라는 클래스가 선언되어 있고 해당 클래스는 데이터를 가져오는 DataImporter 클래스를 갖고 있습니다.
DataImporter 클래스는 실제 디스크 파일에서 데이터를 가져오는 역할을 하기 때문에 초기화시 많은 시간이 소요됩니다.

그래서 디스크에 있는 데이터가 필요할 때 가져오기 위해 lazy 키워드를 var importer = DataImporter() 앞에 붙입니다.

위의 코드 처럼 manager 인스턴스의 data 프로퍼티에 값을 넣어도 DataImporter 인스턴스인 importer 는 초기화 되지 않습니다.

지연 저장 프로퍼티 를 사용하므로써 지금 당장 필요하지 않는데 오래 걸리는 연산을 당장 시키지 않아도 된다는 것이죠!


print(manager.importer.filename)
// the DataImporter 인스턴스가 생성되었습니다.
// "data.txt" 파일을 출력합니다.

다음과 같이 manager 인스턴스에 있는 importer 프로퍼티에 접근할 때 importer 인스턴스가 생성됩니다!

lazy 를 잘 사용하면 메모리 낭비도 줄이고 성능도 향상되고 대기 시간도 줄일 수 있기 때문에 적절한 시기에 사용할 수 있도록 많은 시행착오를 겪여야 할거 같습니다!


✅ 마치며

오늘은 프로퍼티 중 저장 프로퍼티 에 대해 알아보았습니다.
깊게 파고들수록 어려운 내용들이 나와서 힘들었지만 정리하고 보니 뿌듯하네요!

잘못된 부분이 있다면 언제든지 피드백 주시고 궁금한 부분이 있다면 댓글 남겨주세요!

같이 공부하면서 성장하는 시간이 되었으면 좋겠습니다!

두번째 편인 Computed Property ( 연산 프로퍼티 ) 로 찾아올게요!

profile
📱iOS Developer, 🍎 Apple Developer Academy @ POSTECH 1st, 💻 DO SOPT 33th iOS Part

0개의 댓글