프로퍼티를 연산하거나 관찰하는 기능은 프로퍼티뿐만 아니라 전역 및 지역 변수에도 적용할 수 있다.
저장 변수는 저장 프로퍼티와 마찬가지로 특정 타입의 값을 저장할 공간을 제공하고 그 타입 값을 설정하거나 반환하는 역할을 한다.
변수에 저장 프로퍼티 역할이 있다면 연산 프로퍼티의 역할을 하는 변수 역시 존재한다. 연산 변수는 값을 저장하기보다는 값을 담고 있는 변수 값을 연산한다.
전역으로 선언된 변수 및 상수 연산은 언제나 처음 사용될 때까지 연산을 미루는 레이지 특성을 띤다. 레이지 저장 프로퍼티와 비슷하지만, lazy
라는 단어를 붙이지 않는다. 반대로, 지역을 선언된 변수나 상수는 레이지 특성을 띠지 않는다.
프로퍼티 래퍼를 로컬 저장 변수에 적용할 수는 있지만 전역 변수나 연산 변수에 사용할 수는 없다.
func someFunction() {
@SmallNumber var myNumber: Int = 0
myNumber = 10
// now myNumber is 10
myNumber = 24
// now myNumber is 12
}
@SmallNumber
프로퍼티가 어트리뷰트로 적용된 myNumber
변수 값은 프로퍼티 래퍼 내부에 설정한 대로 12보다 큰 수가 입력되면 12를 자동 세팅한다.
앞의 프로퍼티는 특정 인스턴스에 속하는 프로퍼티였다. 그런데 타입 그 자체에 속하는 프로퍼티를 정의할 수도 있다. 인스턴스를 아무리 많이 선언한다 할지라도 타입 프로퍼티는 유일하다.
특정 타입을 인스턴스화했을 때 모든 인스턴스 상에 존재하는, 즉 보편적인 값을 타입 프로퍼티로 설정하면 편리하다. 예를 들어 모든 인스턴스가 사용하는 값을 상수로 둔다던가, 인스터스가 전역으로 접근 가능한 특정 값을 저장하는 프로퍼티가 있을 수 있다.
저장 타입 프로퍼티는 변수나 상수 모두 가능한데, 연산 타입 프로퍼티는 변수만 가능하다. 또한 저장 타입 프로퍼티를 선언할 때 디폴트 값을 주는 게 필수적인데, 타입 그 자체가 이니셜라이저를 가지고 있지 않기 때문이다.
저장 타입 프로퍼티 또한 최초로 접근할 때까지 초깃값을 설정하지 않는 레이지 특성을 띤다. 멀티 스레드 환겨에서 저장 타입 프로퍼티에 접근하더라도 오로지 단 한 번만 초기화되는 게 보장된다는 점에서 이전의 레이지 저장 프로퍼티와는 차이가 있다. 또한 lazy
를 쓰지 않아도 자동으로 처리된다.
타입을 선언할 때 타입 프로퍼티를 쓰자. static
키워드로 타입 프로퍼티를 지정할 수 있고, 클래스 타입이 사용할 연산 타입 프로퍼티에 대해서는 class
키워드를 써서 서브클래스가 상위클래스를 오버라이드할 수 있다.
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"
.
연산자를 통해 프로퍼티 값에 접근하는 건 타입이든 인스턴스든 같지만, 타입 프로퍼티는 인스턴스 값을 쿼리하지 않는다는 데 주의.
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
// cap the new audio level to the threshold level
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
// store this as the new overall maximum input level
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"
AudioChannel
구조체의 두 인스턴스 leftChannel
과 rightChannel
를 따로 만들어 각 인스턴스 상에서 currentLevel
값을 변경했는데, 결과적으로 타입 프로퍼티 maxInputLevelForAllChannels
는 모두 반영된다. 구조체 내부에서 인스턴스의 currentLevel
값이 반영되는 타입 프로퍼티가 (아무리 많은 구조체의 인스턴스가 있다 하더라도) 오로지 하나로 유일하기 때문에 관리가 쉽다.