[Swift] 내 마음 속에 저장 프로퍼티

GUNDY·2023년 11월 13일
1
post-thumbnail

오늘 둘러볼 것은 각종 값을 저장하는 저장 프로퍼티(Stored Properties).

그런데 저장 프로퍼티를 알려면 우선 프로퍼티가 무엇인지 알아야할 것 같다.

프로퍼티란?

Properties associate values with a particular class, structure, or enumeration.

특정 클래스, 구조체, 열거형과 값을 연결하는 것이 바로 프로퍼티.

프로퍼티는 두 종류가 있는데,

프로퍼티는 값을 인스턴스의 일부로 저장하는 저장 프로퍼티와 저장하지 않고 계산하는 연산 프로퍼티로 나뉜다.

연산 프로퍼티는 클래스, 구조체, 열거형에서 모두 사용할 수 있지만 저장 프로퍼티는 클래스와 구조체에서만 사용할 수 있다.

또 프로퍼티는 보통 특정 타입의 인스턴스와 연결되는데, 타입 자체와 연결될 수도 있다. 이를 타입 프로퍼티라고 한다.

연산 프로퍼티나 타입 프로퍼티는 다음에 기회가 되면 다뤄보도록 하고 오늘 집중할 것은 바로

저장 프로퍼티

쉽게 얘기하면 클래스나 구조체의 일부로 저장되는 상수나 변수를 말한다.

변수로 선언한다면 var로, 상수로 선언한다면 let으로 시작하면 된다.

기본 값을 줄 수도, 초기화 중에 초기 값을 설정하고 수정할 수도 있다.

가볍게 예시를 들어보자면

struct Gundam {
    
    let name: String
    var weapon: String = "빔 사벨" // 프로퍼티 기본 값
    
    init(name: String) {
        self.name = name // 초기화 중 초기 값 설정
    }
}

이렇게 기본 깂이 주어진 프로퍼티에 대해서는 초기화 구문에서 따로 초기 값을 설정하지 않아도 괜찮다.

값이 할당되고 나면 let으로 선언한 상수 프로퍼티는 다시는 값을 바꿀 수 없다. 당연하다 상수의 뜻이 뭔데.

그렇다 변하지 아니하는 것이다.

하지만 변수는 얼마든지 다시 바꿀 수 있다.

바꿀 수 있다며 왜 에러나는데?

엄밀히 말하면 얼마든지 다시 바꿀 수 있는 것은 아니고, 구조체의 인스턴스 자체를 상수로 선언한 경우에는 바꿀 수 없다. 변하면 그게 상수냐.

그래서 var로 선언한 제타의 경우에는 아무런 에러가 뜨지 않는다.

이번엔 클래스로 예시를 들어보겠다.

class MobilePod {
    
    let name: String
    var weapon: String = "180mm 무반동포" // 프로퍼티 기본 값
    
    init(name: String) {
        self.name = name // 초기화 중 초기 값 설정
    }
}

클래스도 마찬가지로 기본 값과 초기 값 등을 할당해 줄 수 있다.

얘는 왜 상수 인스턴스인데 에러 안 나? 죽고 싶어 주인장?

그것은...

클래스와 구조체는 다르다. 각각 참조 타입과 값 타입인데, 이 참조와 값의 차이에서 오는 차이인 것이다.

그말은 곧 상수, 변수 프로퍼티도 값 타입인지 참조 타입인지에 따라 그 프로퍼티의 프로퍼티를 수정할 수 있는지 없는지 나뉜다는 것이다.

지연 저장 프로퍼티

저장 프로퍼티 중에서 좀 게으른 놈들이 있다.

처음 사용될 때까지 초기 값이 계산되지 않는 저장 프로퍼티인데, 선언 앞에 lazy 키워드를 덧붙여 사용한다.

그게 왜 필요한데?

초기화될 때까지 값을 알 수 없는 경우가 있다. 가령 self에 접근해야 하는 경우가 있겠다.

만약 특정 경우에는 100을, 일반적으론 20을 할당해야하는 경우라고 쳐보자.

struct Striker {
    
    let name: String
    var battery: Int = self.name == "퍼펙트" ? 100 : 20
    
    init(name: String) {
        self.name = name
    }
}

이런 식으로 self.name"퍼펙트"인지에 따라 값이 달라진다.

하지만 이대로 코드를 작성하면 아래와 같은 에러가 뜬다.

Cannot find 'self' in scope; did you mean to use it in a type or extension context?

당장 self에 접근할 수 없기 때문이다. 초기화가 완료되는 시점까지는 self에 접근할 수 없는데 self와 관련된 값을 할당해야 한다면?

lazy 키워드를 붙이는 것으로 깔끔하게 해결할 수 있다.

처음 사용될 때까지 초기 값이 계산되지 않기 때문에 자연스럽게 초기화 이후로 사용시점이 미뤄지게 된다. 그렇다면?

self에 접근할 수 있다.

그런데 지연 저장 프로퍼티는 let으로는 선언할 수 없다.

이는 초기화가 완료되는 시점까지 반드시 값이 할당되어야 하는 상수 프로퍼티의 특징 때문이다.

그렇기 때문에 지연 저장 프로퍼티는 항상 var로 선언된다.

또한 계산 비용이 많이 드는 경우에도 유용하다.

무조건 사용하는 것이 아니라 사용을 안 할 수도 있는데 비용이 많이 드는 프로퍼티의 경우라면 고려할만 하다.

class DataImporter {
    /*
    DataImporter는 외부 파일에서 데이터를 가져오는 클래스.
    클래스를 초기화하는 데 적지 않은 시간이 걸리는 것으로 가정한다.
    */
    var filename = "data.txt"
    // DataImporter 클래스는 여기서 데이터 가져오기 기능을 제공한다.
}


class DataManager {
    lazy var importer = DataImporter()
    var data: [String] = []
    // DataManager 클래스는 여기서 데이터 관리 기능을 제공한다.
}


let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// importer 프로퍼티에 대한 DataImporter 인스턴스가 아직 생성되지 않았다.

공식 문서에 나온 예시를 가져왔다.

그런데 주의할 점이 있다.

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property hasn’t yet been initialized, there’s no guarantee that the property will be initialized only once.

아직 초기화되지 않은 지연 저장 프로퍼티를 여러 스레드에서 동시에 액세스한다면 한 번만 초기화 될 것이 보장되지 않는다. 그러므로 비동기 프로그래밍에서 지연 저장 프로퍼티를 다룬다면 주의 요망!

마무리

오늘은 저장 프로퍼티에 대해 알아보았다.

정리하면서 든 생각은

연산 프로퍼티는 또 언제 다루지...

profile
개발자할건디?

0개의 댓글