Property Series - Stored Property

이원희·2020년 12월 27일
1

 🐧 Swift

목록 보기
7/32
post-thumbnail

오늘은 Swift의 Property에 알아보자.
Property에 대해 정리하고자 한 이유는 코드리뷰에서 연산프로퍼티와 파라미터가 없는 메서드의 차이를 물어봐서도 있고, 싱글톤에 대해 포스팅하려하니 프로퍼티에 대해 알고 넘어가는게 좋을거 같아서 포스팅한다.

Properties

프로퍼티는 값을 특정 class, struct, Enum과 연결한다.

Swift Property는 3가지가 있다.

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

Stored Property

인스턴스의 변수, 상수이다.
class, struct에서만 사용할 수 있다.

Computed Property

Stored Property처럼 값을 저장하는 것이 아니라 특정 연산을 실행한 결과값이다.
즉, Computed Property는 값을 연산한다.
class, struct, Enum에 사용할 수 있다.

Type Property

특정 타입에 사용되는 Property이다.


이 포스팅에서는 Stored Property에 대해 알아보자.

Stored Property(저장 프로퍼티)

class 또는 struct 인스턴스와 연관된 값을 저장한다.

var?! let?!

var, let을 사용할 수 있다.
var로 선언하면 변수를 저장하는 것이고, let으로 선언하면 상수를 저장하는 것이다.

저장 프로퍼티를 선언할 때 저장할 기본값을 지정할 수 있다.

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)

rangeOfThreeItems.firstValue = 6

(예제는 Swift.org에서 가져왔다.)
위의 예제에서 저장 프로퍼티firstValuelength이다.

var로 선언된 firstValue변수 저장 프로퍼티이고, let으로 선언된 length상수 저장 프로퍼티이다.
varrangeOfThreeItems라는 FixedLengthRange 인스턴스를 만들었다.

인스턴스를 만들때 firstValue에는 0, length에는 3을 선언했다.
변수 저장 프로퍼티인 firstValue 값을 바꿔보자.

rangeOfThreeItems.firstValue = 6

을 통해 rangeOfThreeItems(FixedLengthRange의 인스턴스)의 firstValue 값이 바뀐 모습을 확인할 수 있다.

그렇다면 상수 저장 프로퍼티인 length의 값을 바꿀 수 있을까?

❗️에러가 뜬다!
에러를 확인해보니 상수여서 값을 변경할 수 없다고 한다!


이니셜라이저

struct저장 프로퍼티Optional이 아니여도 저장 프로퍼티를 모두 포함한 이니셜라이저를 자동으로 생성한다.

struct Person {
    var name: String
    var age: Int
}

let wonhee: Person = Person(name: "wonhee", age: 20)

struct에 이니셜라이저를 따로 생성하지 않아도 자동으로 생성해준다.

class저장 프로퍼티Optional이 아니면 Property 기본값을 지정해주거나 사용자 정의 이니셜라이저를 통해 반드시 초기화해줘야 한다.
(상수 프로퍼티는 인스턴스가 초기화 될 때 한 번만 값이 할당 된다.)
(자식 클래스에서 초기화 변경이 불가하다.)

class Korean {
    var citizen: Person
    let cityName: String
}

let wonheeCitizen: Korean = Korean(citizen: Person(name: "wonhee", age: 20), cityName: "Anyang")

그렇다면 위의 struct처럼 class도 자동으로 이니셜라이저가 생성될까?

이니셜라이저가 없어 에러가 뜬다!
그렇다면 정상 코드로 바꿔보자.

class Korean {
    var citizen: Person
    let cityName: String
    init(citizen: Person, cityName: String) {
        self.citizen = citizen
        self.cityName = cityName
    }
}

let wonheeCitizen: Korean = Korean(citizen: Person(name: "wonhee", age: 20), cityName: "Anyang")

위의 코드처럼 이니셜라이저를 생성해주면 해결할 수 있다.

위와 같은 방법으로는 인스턴스 안의 저장 프로퍼티를 사용하는데 번거로워보인다.

저장 프로퍼티에 기본값을 준다면 사용하기 편해지지 않을까?
한 번 확인해보자.

struct

struct Person {
    var name: String = "name"
    var age: Int = 10
}

let some: Person = Person()
let wonhee: Person = Person(name: "wonhee", age: 20)

Person 구조체에 name, age 저장 프로퍼티에 각각 "name", 10이라는 기본값을 주었다.

코드를 확인해보면 파라미터가 없는 이니셜라이저로 생성한 인스턴스는 저장 프로퍼티의 기본값이 들어가 있음을 확인할 수 있다.

class

class Korean {
    var citizen: Person = Person()
    var cityName: String = "city"
}

let wonheeCitizen = Korean()

Korean class 저장 프로퍼티에도 기본값을 주었더니 이니셜라이저가 없어도 잘 동작함을 확인할 수 있다.
하지만 아래와 같은 코드가 없다면 wonheeCitizen 인스턴스의 cityName 저장 프로퍼티에는 "city"가 저장되어 있다.

let wonheeCitizen = Korean()
wonheeCitizen.cityName = "Anyang"

위와 같은 코드를 통해 cityName 저장 프로퍼티 값을 변경할 수 있다.

초기값을 미리 지정해두면 인스턴스 생성 과정이 편해진다.
하지만 초기값이 아닌 다른 값을 사용하고 싶을때에는 직접 프로퍼티를 할당해주는 과정이 불편하다.
이런 불편함은 원하지 않은 값을 사용하게 되는 에러의 원인이 될 수도 있다.
또한, cityName을 상수로 사용하고 싶어져도 직접 프로퍼티를 할당해주는 과정을 위해 let으로 선언할 수 없다.

위와 같은 불편함은 Optional Property는 이니셜라이저에서 값을 할당하지 않아도 되는 특성으로 해소할 수 있다.

struct Person {
    var name: String
    var age: Int
}

class Korean {
    var citizen: Person?
    let cityName: String
    
    init(cityName: String) {
        self.cityName = cityName
    }
}

let wonhee: Korean = Korean(cityName: "Anyang")
wonhee.citizen = Person(name: "wonhee", age: 20)

Lazy Stored Property

Lazy Stored Property는 지연 저장 프로퍼티라고 부른다.

지연 저장 프로퍼티는 호출이 있어야 값을 초기화한다.

lazy라는 키워드를 사용해서 선언한다.
상수는 인스턴스가 완전히 생성되기 전에 초기화해야 하므로 필요할때 값이 할당되는 지연 저장 프로퍼티에는 사용할 수 없다.
즉, 지연 저장 프로퍼티var만 사용할 수 있다.

코드로 이해해보기

그렇다면 코드를 통해 이해해보자.

class 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")

DataManager class에 importer라는 지연 저장 프로퍼티가 있다.
managerDataManager 인스턴스를 선언했다.

manager.data.append("Some data")
manager.data.append("Some more data")

에서는 지연 저장 프로퍼티 importer를 호출하지 않아 importer는 값이 초기화되지 않았다.


class 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")

print(manager.importer.filename)

마지막 줄의

print(manager.importer.filename)

에서 지연 저장 프로퍼티 importer를 호출해서 이때 importer의 값이 초기화되었다.


언제 사용할까?

주로 복잡한 class 혹은 struct에서 사용한다.

  • 지연 저장 프로퍼티는 초기값이 인스턴스의 초기화가 될때까지 값을 모르는 외부요소에 의존하는 경우에 유용하게 사용할 수 있다.
  • 초기값이 복잡하거나 계산 비용이 많이 드는 설정에서도 유용하게 사용할 수 있다.

이런 점을 알고 지연 저장 프로퍼티를 사용한다면 성능을 높이고 공간 낭비를 줄일 수 있다.


주의할 점은?

위에서 지연 저장 프로퍼티에 대해 말할때에도 나왔지만 var을 사용해서 선언해야한다.

멀티 thread 환경에서 여러 thread가 동시다발적으로 지연 저장 프로퍼티에 접근한다면 프로퍼티가 한 번만 초기화 된다는 것을 보장할 수 없다.


마무리

이전부터 프러퍼티에 대해 정리하려고 했는데 이제서야 했다.
정리하지 않아도 사용하는데 문제는 없었지만 그래도 한 번 정리하는 편이 나만의 기준을 세우는데 좋을거 같아서 정리해봤다.
정리하다보니 프로퍼티에 대한 내용이 꽤되서 시리즈처럼 나눠서 정리하려한다.
그럼 이만👋

0개의 댓글