Swift

프로퍼티 (Properties)

데쿠! 2020. 11. 10. 19:10

 

프로퍼티는 클래스, 구조체, 열거형과 관련한 값으로, 프로퍼티의 종류에는 저장 프로퍼티(Stored Properties)와 계산된 프로퍼티 (Computed Properties)가 있습니다. 

저장 프로퍼티 : 저장 프로퍼티는 값을 저장하고 있는 프로퍼티를 의미하고,

계산된 프로퍼티 : 값을 저장하지 않고, 특정하게 계산한 값을 반환해주는 프로퍼티입니다. 

계산된 프로퍼티는 클래스, 구조체, 열거형에서 사용 가능하지만 저장 프로퍼티는 클래스와 구조체에서만 사용 가능합니다.

 

저장 프로퍼티 (Stored Properties)

저장 프로퍼티는 일반적인 변수와 같습니다. 상수는 let 키워드를 사용하고, 변수는 var 키워드를 사용해 사용합니다.

struct Number{
    var one: Int
    var two: Int
    let three: Int
}

let num: Number = Number(one: 1, two: 2, three: 3)

-> num은 상수이므로 더이상 값을 바꿀 수 없습니다.

-> 만약 num.three = 4로 한다면 상수의 값을 바꾸는 것이 불가능하기 때문에 에러가 발생합니다.

class Number{
    var one: Int
    var two: Int
    
    init(_ one: Int, _ two: Int){
        self.one = one
        self.two = two
    }
}

let num = Number(1,2)
num.one = 2

-> 하지만 클래스의 경우, num이라는 상수가 Number(1,2)를 참조하는 것이기 때문에 num자체를 바꾸지 않는 이상 내부 프로퍼티는 바꿀 수 있습니다. 

 

지연 저장 프로퍼티 (Lazy Stored Properties)

값이 처음으로 사용되기 전에는 계산되지 않는 프로퍼티입니다. lazy키워드를 붙여 사용합니다.

지연 프로퍼티는 나중에 값을 가지기 때문에 var로 선언해야 합니다.

복잡한 계산이나 부하가 많이 걸리는 작업을 지연 프로퍼티로 선언해서 사용하면 실제 사용되기 전에는 실행되지 않아서 초기에 복잡한 계산을 피할 수 있습니다.

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 인스턴스는 이 시점에 생성돼 있지 않습니다.

-> swift 문서에 나와있는 예시를 보자면, DataManager class에서 importer라는 이름으로 데이터를 가져오는 인스턴스를 선언했습니다.

-> importer변수가 실제 사용되기 전까지는 인스턴스는 생성되지 않습니다.

 

 

계산된 프로퍼티 (Computed Properties)

클래스, 구조체, 열거형은 계산된 프로퍼티를 선언할 수 있습니다.

실제 값을 저장하고 있는 것이 아니라 getter와 optional 한 setter를 제공해 값을 탐색하고 간접적으로 다른 프로퍼티 값을 설정할 수 있는 방법을 제공합니다.

var _property: Int
var property: Int{
    get{
        return _property
    }
    set(newProperty){
        _property = newProperty
    }
}

print(property)
property = 4444

-> get과 set을 사용하는 방식입니다.

-> get과 set에서 자기 자신을 return하거나 설정하면 recursive 하게 get, set을 호출하므로 실제 값이 저장되는 변수를 하나 만들고 만약 외부에서 property의 값을 설정하거나 접근하면 새로운 변수인 _property에 값이 설정되도록 합니다.

 

Setter 선언의 간략한 표현 (Shorthand Setter Declaration)

위의 예시의 set에서 만약 newProperty를 따로 지정하지 않으면 기본값으로 newValue가 사용됩니다. 

 

 

읽기 전용 계산된 프로퍼티 (Read-Only Computed Properties)

get만 있고 set이 없는 계산된 프로퍼티를 읽기 전용 계산된 프로퍼티라고 합니다. 

struct Number {
    var a = 1
    var b = 2
    var sum = Int {
         return a + b
    }
}

print(sum) // 3

-> 읽기 전용에서는 get 키워드를 사용하지 않아도 됩니다. 

 

 

 

프로퍼티 옵저버 (Property Observers)

프로퍼티에는 새 값이 설정될 때마다 감지할 수 있는 옵저버를 제공합니다. 이 옵저버는 새 값이 이전 값과 같더라도 호출됩니다.

(이 프로퍼티 옵저버는 lazy stored properties)에서는 사용할 수 없습니다.)

계산된 프로퍼티는 setter에서 값의 변화를 감지할 수 있기 때문에 따로 옵저버를 정의할 필요가 없습니다.

  • willSet : 값이 저장되기 바로 직전에 호출됨 
  • didSet: 새 값이 저장되고 난 후에 호출됨

willSet에서는 새 값의 파라미터명을 따로 지정할 수 있는데, 지정하지 않으면 newValue를 사용합니다.

didSet에서는 바뀌기 전 값의 파라미터명을 지정할 수 있는데, 지정하지 않으면 oldValue를 사용합니다.

class Position{
    var x: Int = 0{
        willSet(newValue){
            print("new x position is \(newValue)")
        didSet(oldValue){
            print("You've traveled \(newValue - oldValue)km")
        }
    }
}
let position = Position()
position.x = 5
//new x position is 5
//You've traveled 5km

position.x = 10
//new x position is 10
//You've traveled 5km

-> 만약 x 변수에 새로운 값을 설정하면 willSet이 실행되고 didSet이 실행됩니다.

 

전역 변수와 지역변수 (Global and Local Variables)

전역 변수는 지연 계산이 되고 지역변수는 지연 계산될 수 없습니다.

 

 

타입 프로퍼티 (Type Properties)

타입 프로퍼티전에 인스턴스 프로퍼티에 대한 개념이 필요합니다.

인스턴스 프로퍼티란 특정 타입의 인스턴스에 속하는 프로퍼티인데, 특정한 구조체, 클래스에 속하는 프로퍼티들이 인스턴스 프로퍼티입니다.

타입 프로퍼티는 특정 타입에 속한 프로퍼티로 그 타입에 해당하는 단 하나의 프로퍼티만 생성됩니다. 이 타입 프로퍼티는 모든 인스턴스에 공통으로 사용되는 값을 정의할 때 유용합니다. 

(항상 초기값을 지정해서 사용해야 합니다.)

 

타입 프로퍼티 구문 (Type Property Syntax)

타입 프로퍼티는 static 키워드를 사용해서 선언을 합니다.

struct와 enum과 달리 class에서는 static, class 두가지 방식으로 타입 프로퍼티를 선언할 수 있는데, 만약 class로 선언을 했다면 해당 클래스를 상속받은 어떤 클래스에서 해당 타입 프로퍼티를 오버 라이딩하여 값을 재정의 할 수 있다는 의미입니다. 

struct SomeStructure{
    static var storedTypeProperty = "value"
    static var computedTypeProperty: Int{
        return 1
    }
}

enum SomeEnumeration{
    static var storedTypeProperty = "value"
    static var computedTypeProperty: Int{
        return 2
    }
}

class SomeClass{
    static var storedTypeProperty = "value"
    static var computedTypeProperty: Int {
        return 3
    }
    class var overrideableComputedTypeProperty: Int{
        return 4
    }
}

-> 위의 예시에서 class의 경우에 class형태인 타입 프로퍼티는 상속받은 자식 class에서 재정의할 수 있습니다.

class SomeChild: SomeClass{
    override static var overrideableComputedTypeProperty: Int{
        return 5
    }
}

 

 

타입 프로퍼티의 접근과 설정 (Querying and Setting Type Properties)

타입 프로퍼티는 점연산자(dot operator)를 통해 프로퍼티의 값을 가져오고 할당합니다.

print(Somestructure.storedTypeProperty)
//print "value"

SomeStructure.storedTypeProperty = "2 value"