프로퍼티(Property)
는 클래스, 구조체, 열거형과 연관된 값이다. 저장 프로퍼티(stored property)
는 인스턴스의 일부로써 상수나 변수를 저장한다. 계산 프로퍼티(computed property)
는 값을 계산하는 기능을 한다. 저장 프로퍼티는 클래스와 구조체에서만 사용 가능하며, 계산 프로퍼티는 클래스와 구조체, 열거형 모두에서 사용할 수 있다.
프로퍼티는 보통 특정 타입의 인스턴스와 연관되어 있다. 타입 프로퍼티처럼 그 자신의 타입과 관련될 수도 있다.
프로퍼티 옵저버를 정의해서 프로퍼티의 값의 변화를 관찰할 수도 있다. 프로퍼티 옵저버는 저장된 프로퍼티, 또는 서브 클래스가 부모 클래스로부터 상속받은 프로퍼티에 더해질 수 있다.
여러 프로퍼티의 게터와 세터 코드를 재사용하기 위해 프로퍼티 래퍼를 사용할 수도 있다.
저장 프로퍼티(stored property)
는 특정 클래스나 구조체 인스턴스의 일부분으로써 저장된 상수 또는 변수이다. 저장 프로퍼티는 다른 변수/상수 저장 프로퍼티가 될 수도 있다.
저장 프로퍼티를 정의할 때 기본 값을 지정할 수도 있다. 초기화 과정에서 저장 프로퍼티에 초기 값을 설정하거나 수정하는 것도 가능하다. 이는 상수 저장 프로퍼티에도 허용된다.
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8
FixedLengthRange
의 인스턴스는 변수 저장 프로퍼티인 firstValue
와 상수 저장 프로퍼티인 length
를 갖고 있다. 위의 예시에서 length
는 새로운 인스턴스가 만들어질 때 초기화되고 이후에는 수정할 수 없다.
구조체 인스턴스를 상수로 할당할 경우, 프로퍼티를 수정할 수 없다. 변수 프로퍼티도 불가능하다.
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property
rangeOfFourItems
은 상수로 선언됐으며, firstValue
프로퍼티가 변수로 선언되었음에도 바꾸는 것이 불가능하다. 왜냐면 구조체는 값 타입(value type)
이기 때문이다. 값 타입의 인스턴스가 상수로 지정되면, 그것의 모든 프로퍼티도 상수가 된다.
클래스는 참조 타입이기 때문에 다르다. 참조 타입을 상수로 선언해도 인스턴스의 변수 프로퍼티는 변경 가능하다.
지연된 저장 프로퍼티(lazy stored property)
는 그것이 처음으로 사용되기 전까지는 초기 값이 계산되지 않는 프로퍼티다. 선언부 앞에 lazy
키워드를 작성하여 지연된 저장 프로퍼티를 만든다.
지연된 저장 프로퍼티는 반드시 변수로 선언해야 한다. 인스턴스 초기화가 완료된 이후에야 검색되기 때문이다. 상수 프로퍼티는 반드시 초기화 완료 전에 값을 갖고 있어야 한다. 그렇기에 상수로 선언할 수 없다.
지연된 저장 프로퍼티는 프로퍼티의 초기 값이 인스턴스 초기화가 완료된 이후까지 알기 힘든 외부 요소에 의존적일 때 유용하다. 복잡하거나 계산 비용이 높지만 필요해질 때까지 작동하지 않는 프로퍼티의 초기 값을 다룰 때도 좋다.
아래 예시는 복잡한 클래스에서 불필요한 초기화를 피하기 위해 지연된 저장 프로퍼티를 사용하는 것을 보여 준다.
class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a nontrivial amount of time to initialize.
*/
var filename = "data.txt"
// the DataImporter class would provide data importing functionality here
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// the DataManager class would provide data management functionality here
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created
DataManager
클래스는 빈 문자열 배열로 초기화되는 data
저장 프로퍼티를 갖고 있다. DataManager
의 목적은 문자열 데이터 배열에 대한 접근 또는 관리를 제공하는 것이다.
DataManager
클래스 기능의 일부는 파일로부터 데이터를 불러오는 것이다. 이 기능은 초기화하는 데 어느정도의 시간이 필요한 DataImporter
클래스로부터 제공된다. DataImporter
인스턴스는 파일을 열고, 그것의 내용을 메모리에 읽기 때문에 인스턴스를 초기화할 때 시간이 소요된다.
DataManager
클래스는 파일로부터 데이터를 불러오지 않아도 데이터를 다루는 것이 가능하다. DataManager
인스턴스를 만들 때 DataImporter
인스턴스까지 만들 필요가 없다는 뜻이다. 대신, DataImporter
가 처음으로 사용될 때 그것을 만들게 하면 된다.
lazy
키워드가 표기됐기 때문에 importer
프로퍼티의 DataImporter
인스턴스는 프로퍼티에 처음접근할 때만 만들어진다.
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
만악 초기화되지 않은
lazy
프로퍼티가 여러 스레드에서 동시에 접근할 경우, 프로퍼티가 하나만 생성된다고 보장할 수 없다.
Objective-C는 값을 저장하거나 클래스 인스턴스를 참조하기 위한 두 가지 방법을 제공한다. 점 연산자(instance.property = value
)나 set 연산(instance.setProperty(value)
)을 사용한다. 뿐만 아니라 뿐만 아니라 메모리 관리와 관련한 개념도 프로퍼티에 함께 명시한다. @property (nonatomic, retain) NSString *propertyName
와 같은 형태이다.
Swift는 이러한 개념을 하나의 프로퍼티 선언에 통합한다. Swift 프로퍼티는 해당 인스턴스 변수를 갖지 않고, 프로퍼티의 저장 공간에 직접적으로 접근하지 않는다. 이러한 접근법은 서로 다른 문맥에서 값에 접근하는 방식에 따른 혼란을 피하고, 프로퍼티의 선은을 하나의 일정한 구문으로 간소화 한다. 프로퍼티에 대한 모든 정보(이름, 타입, 메모리 관리 특성을 포함한)는 타입 정의의 일부분으로써 하나의 장소에 정의된다.
클래스, 구조체, 열거형은 계산 프로퍼티(computed property)
를 정의할 수 있다. 여기에는 실제 값이 저장되지 않는다. 대신 다른 프로퍼티와 값을 간접적으로 검색하고 설정할 수 있는 게터와 옵셔널 세터를 제공한다.
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"
위 예시는 기하학적 모양에 대한 세 개의 구조체를 정의한다.
Point
는 x와 y좌표를 캡슐화한다.Size
는 너비와 높이를 캡슐화한다.Rect
는 origin point
와 size
를 통해 사각형을 정의한다.Rect
구조체는 계산 프로퍼티인 center
를 제공한다. 처음 가운데 위치는 origin
과 size
에 의해 결정된다. 때문에 가운데 위치를 명시적인 Point
값으로 저장할 필요가 없다.대신 Rect
는 center
계산 프로퍼티를 위한 커스텀 게터와 세터를 정의한다.
예시에서는 새로운 Rect
변수인 square
를 만들었다. 이 변수는 (0, 0)
의 origin point
와 10
의 width
와 height
로 초기화된다. 이 정사각형은 아래 그래프의 파란색 사각형과 같다.
center
프로퍼티는 새로운 값인 (15, 15)
로 정해진다. 아래 그래프의 오렌지색 사각형과 같다. center
프로퍼티를 세팅하는 것은 center
의 세터를 호출하고, origin
프로퍼티에 저장된 x
, y
값을 수정한다. 정사각형은 새로운 위치로 옮겨진다.
계산 프로퍼티의 세터가 새로운 값의 이름을 정의하지 않으면, newValue
라는 이름이 기본적으로 사용된다.
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
게터의 전체 바디가 한 줄로 표현된다면 그 표현을 암시적으로 반환할 수 있다.
struct CompactRect {
var origin = Point()
var size = Size()
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
게터는 있지만 세터는 없는 계산 프로퍼티를 읽기 전용 계산 프로퍼티(read-only computed property)
라고 부른다. 읽기 전용 프로퍼티는 점 문법으로 접근할 수 있고 항상 값을 반환하지만, 새로운 값을 설정할 수는 없다.
읽기 전용 계산 프로퍼티를 포함한 모든 계산 프로퍼티는 반드시 변수로 선언되어야 한다. 이러한 값은 고정되어 있지 않기 때문이다.
let
키워드는 오직 상수 프로퍼티에만 사용되며, 인스턴스 초기화 시에 한번 정해지면 더 이상 값을 바꿀 수 없다.
get
키워드를 제거함으로써 읽기 전용 계산 프로퍼티를 간단하게 선언할 수 있다.
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"
프로퍼티 옵저버(property observer)
는 프로퍼티 값의 변화를 관찰하고 거기에 반응한다. 프로퍼티 옵저버는 프로퍼티 값이 설정될 때마다 호출된다. 현재 값과 똑같은 값이 들어오더라도 마찬가지다.
지연된 저장 프로퍼티를 제외한 모든 저장 프로퍼티에 프로퍼티 옵저버를 더할 수 있다. 부모클래스로부터 오버라이딩한 모든 프로퍼티에도 가능하다. 오버라이드되지 않은 계산 프로퍼티를 위한 프로퍼티 옵저버를 정의할 필요는 없다. 계산 프로퍼티의 세터가 값의 변화를 이미 관찰하고 있기 때문이다.
프로퍼티 옵저버엔 두 가지 옵션이 있다.
willSet
은 값이 저장되기 이전에 호출된다.didSet
은 새로운 값이 저장된 이후에 즉시 호출된다.willSet
옵저버를 구현했다면 새 값의 매개 변수 이름을 지정할 수 있는데, 지정하지 않으면 기본 값으로 newValue
를 사용한다. didSet
옵저버를 구현한다면 바뀌기 전의 매개 변수 이름을 정할 수 있다. 지정하지 않으면 oldValue
가 기본값으로 사용된다.
자식 클래스에서 특정 프로퍼티의 값을 설정했을 때, 부모 클래스의 initializer가 호출된 후
willSet
,didSet
프로퍼티 옵저버가 실행된다. 부모 클래스에서 프로퍼티를 변경하는 것도 마찬가지로 부모 클래스의 initializer가 호출된 후 옵저버가 실행된다.
아래는 willSet
과 didSet
을 사용하는 예시를 보여 준다. 새로운 클래스 StepCounter
를 정의한다. 보수계 또는 다른 걸음 계산기로부터 들어오는 데이터를 사용하여 하루 루틴에서의 운동을 추적한다.
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
함수의 만약 인아웃 매개 변수에 프로퍼티를 넘기면
willSet
과didSet
은 항상 호출된다. 인아웃 매개 변수에서는 프로퍼티가 항상 복사되기 때문이다. 함수가 끝날 때 프로퍼티는 원래 값에 새 값을 덮어 쓰게 된다.
프로퍼티 래퍼(property wrapper)
는 프로퍼티가 저장되는 방식을 다루는 코드와 프로퍼티를 정의하는 코드를 분리하는 단계를 더한다. 예를 들어, 스레드 안전 체크를 하거나 데이터베이스에 있는 데이터를 저장하는 프로퍼티가 있다면, 모든 프로퍼티에 그러한 코드를 작성해야 한다. 프로퍼티 래퍼를 사용한다면, 래퍼를 정의할 때 관리 기능을 하는 코드는 한 번만 작성하면 된다. 그리고 여러 프로퍼티에 그 코드를 적용함으로써 재사용할 수 있다.
wrappedValue
프로퍼티를 정의하여 프로퍼티 래퍼를 만든다. 아래의 코드에서, TwelveOrLess
구조체는 그것이 감싸는 값이 항상 12 이하의 숫자를 포함한다는 것을 보장한다.
@propertyWrapper
struct TwelveOrLess {
private var number: Int
init() { self.number = 0 }
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
세터는 새로운 값이 항상 12 이하임을 보장하고, 게터는 저장된 값을 반환한다.
위 예에서
number
의 선언은private
으로 표시되었다.number
프로퍼티는 오직TwelveOrLess
의 구현에서만 사용될 수 있다는 의미다.
래퍼의 이름을 프로퍼티 전에 속성처럼 표기함으로써 프로퍼티에 래퍼를 적용할 수 있다.
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
height
와 width
프로퍼티는 TwelveOrLess
로부터 초기 값을 갖게 된다. 숫자 10을 rectangle.height
에 저장하는 것은 성공하지만, 24를 저장하려 하면 세터의 규칙에 의해 12가 대신 저장된다.
프로퍼티에 래퍼를 적용할 때, 컴파일러는 래퍼를 제공하는 저장 공간과 래퍼를 통해 프로퍼티에 접근하는 코드를 합성한다. 프로퍼티 래퍼의 행위를 특별한 속성 문법의 장점을 취하지 않고 사용하여 코드를 작성할 수도 있다.
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
_height
와 _width
프로퍼티는 TwelveOrLess
프로퍼티 래퍼 인스턴스를 저장한다. height
와 width
에 대한 게터와 세터로 wrappedValue
프로퍼티에 접근할 수 있다.
위의 예시는 TwelveOrLess
의 정의에서 number
에 초기 값을 설정했다. 이런 프로퍼티 래퍼를 사용하는 코드는 TwelveOrLess
에 의해 포장된 프로퍼티에 서로 다른 초기 값을 특정할 수 없다. 예를 들어, SmallRectangle
은 height
와 width
에 초기 값을 전달하지 못한다. 초기 값과 다른 커스터마이징을 위해, 프로퍼티 래퍼는 initializer를 추가할 필요가 있다.
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
초기 값 없이 프로퍼티에 래퍼를 적용할 때, Swift는 init()
initializer를 사용한다.
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
프로퍼티에 초기 값을 지정한다면 Swift는 init(wrappedValue:)
initializer를 사용해 래퍼를 설정한다.
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"
커스텀 속성 소괄호 안에 인자를 전달하면, Swift는 이 인자를 받아들이는 initializer를 사용해 래퍼를 설정한다. 예를 들어 초기 값과 maximam 값을 준다면 Swift는 init(wrappedValue:maximum:)
initializer를 사용한다.
struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"
프로퍼티 래퍼에 인자를 포함하여 래퍼의 초기 상태를 설정하고 그것이 생성될 때 래퍼에 다른 옵션을 넣을 수 있다. 이 문법은 프로퍼티 래퍼를 사용하는 가장 일반적인 문법이다.
프로퍼티 래퍼 인자를 포함할 때 할당 연산자를 사용하여 초기 값을 지정할 수 있다. Swift는 할당 연산을 wrappedValue
인자와 같이 다루며, 포함한 인자를 수용하는 initializer를 사용한다.
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"
height
를 포장하는 SmallNumber
의 인스턴스는 최댓값이 12인 SmallNumber(wrappedValue: 1)
을 호출하며, width
를 감싸는 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 9)
를 호출한다.
프로퍼티 래퍼는 projected value
를 정의함으로써 새로운 기능을 드러낼 수 있다. 예를 들어, 데이터베이스에 대한 접근을 관리하는 프로퍼티 래퍼는 그것의 projected value에 flushDatabaseConnection()
메소드를 노출할 수 있다. projected value의 이름은 달러 사인($)으로 시작한다는 점만 빼면 wrapped value와 같다. 개발자가 정의하는 코드에서는 $로 시작하는 프로퍼티를 정의할 수 없기 때문에, 이 값은 다른 프로퍼티에 간섭하지 않는다.
아래의 코드는 새로운 값을 프로퍼티에 저장하기 전에 프로퍼티 래퍼가 조정을 했는지 여부를 추적하기 위해 projectedValue
프로퍼티를 SmallNumber
구조체에 더한다.
@propertyWrapper
struct SmallNumber {
private var number: Int
var projectedValue: Bool
init() {
self.number = 0
self.projectedValue = false
}
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
프로퍼티 래퍼는 어떤 타입의 projected value든 반환할 수 있다. 예제에서는 프로퍼티 래퍼가 정보 하나만을 노출하면 되기 때문에 Bool
타입을 projectedValue
로 사용했다. 더 많은 정보를 드러내야 할 필요가 있는 래퍼는 다른 데이터 타입의 인스턴스를 반환할 수 있다. 또는 자기 자신의 projected value로써 래퍼의 인스턴스를 self
를 사용해 노출할 수 있다.
프로퍼티 게터 또는 인스턴스 메소드와 같이 타입의 일부인 코드로부터 projected value에 접근할 때는 self
를 생략할 수 있다. 일반적인 프로퍼티 접근 방식과 동일하다.
enum Size {
case small, large
}
struct SizedRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
mutating func resize(to size: Size) -> Bool {
switch size {
case .small:
height = 10
width = 20
case .large:
height = 100
width = 100
}
return $height || $width
}
}
프로퍼티 래퍼는 게터와 세터를 위한 쉽고 편리한 문법이기 때문에, 다른 프로퍼티와 동일한 방법으로 height
와 width
에 접근할 수 있다.
계산 프로퍼티와 프로퍼티 옵저버의 기능은 전역 변수와 지역 변수 모두에 사용 가능하다. 전역 변수는 함수, 메소드, 클로저, 타입 컨텍스트 밖에서 정의된 변수이고, 지역 변수는 그 안에서 정의된 변수이다.
지난 챕터들에서 만났던 전역/지역 변수는 모두 저장된 변수(stored variable)
였다. 저장된 변수는 저장 프로퍼티와 마찬가지로 특정 타입의 값에 대한 저장 공간을 제공하고, 값을 설정하거나 검색할 수 있도록 한다.
계산된 변수(computed variable)
나 저장된 변수를 위한 옵저버(observer)
도 전역/지역 변수로 정의할 수 있다. 계산된 변수는 값을 저장하기 보다는 계산한다. 계산 프로퍼티와 같은 방식으로 작성된다.
전역 상수, 변수는 지연된 저장 프로퍼티와 비슷하게 지연 계산된다. 하지만 지연된 저장 프로퍼티와 다르게
lazy
키워드를 붙일 필요가 없다.지역 상수, 변수는 지연 계산되지 않는다.
인스턴스 프로퍼티는 특정한 타입의 인스턴스에 속한 프로퍼티를 말한다. 이러한 타입의 인스턴스를 생성할 때마다, 다른 인스턴스로부터 분리된 그 자신의 프로퍼티 값을 갖게 된다.
타입의 인스턴스 하나가 아니라 타입 자체에 속한 프로퍼티를 정의할 수 있다. 그 타입에 해당되는 단 하나의 프로퍼티만 생성된다. 이러한 프로퍼티를 타입 프로퍼티(type property)
라 부른다.
타입 프로퍼티는 모든 인스턴스가 사용하는 상수 프로퍼티처럼 특정 타입의 인스턴스에 공통되는 값을 정의할 때 유용하다. 또는 그 타입의 모든 인스턴스에 공통되는 값을 저장하는 변수 프로퍼티를 정의할 때도 좋다.
저장 타입 프로퍼티는 변수 또는 상수가 가능하다. 계산 프로퍼티는 변수 프로퍼티로만 선언할 수 있다.
저장 인스턴스 프로퍼티와 달리, 저장 타입 프로퍼티에는 반드시 기본 값을 주어야 한다. 타입 자신은 초기화 시간에 저장 타입 프로퍼티에 값을 할당할 수 있는 initializer를 가질 수 없기 때문이다.
저장 타입 프로퍼티는 최초 접근시에 지연 초기화된다. 여러 스레드가 동시에 접근해도 한 번만 초기화되는 것을 보장한다. 또한
lazy
키워드를 필요로 하지 않는다.
C나 Objective-C에서는 정적 상수/변수를 전역 정적 변수인 타입과 연관지어 정의한다. 그러나 Swift에서는 타입 프로퍼티는 타입 정의의 일부분으로써 작성된다. 각각의 타입 프로퍼티는 명시적으로 그 타입이 지원하는 범위가 정해져 있다.
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"
아래의 예시는 오디오 채널을 모델링하는 구조체의 일부로써 두 개의 저장 타입 프로퍼티를 사용한다. 각각의 채널은 0에서 10 사이의 오디오 레벨을 갖는다.
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
}
}
}
}
AudioChannel
구조체는 두 개의 저장 타입 프로퍼티를 정의한다. 첫 번째로 thresholdLevel
은 오디오 레벨이 가질 수 있는 최대 레벨 값을 정의한다. 이것은 모든 AudioChannel
인스턴스에 대해 10의 상수 값을 가진다. 만약 오디오 신호로 10보다 더 큰 값이 들어온다면, 제한 값으로 수정된다.
두 번째 타입 프로퍼티는 maxInputLevelForAllChannels
라는 뱐수 저장 프로퍼티이다. 이것은 모든 AudioChannel
인스턴스에 의해 받는 최대 입력 값을 추적한다. 초기화 값은 0이다.
AudioChannel
구조체는 채널의 현재 오디오 레벨을 보여주는 currentLevel
저장 프로퍼티 역시 정의한다.
currentLevel
프로퍼티는 값의 변화를 확인하는 didSet
프로퍼티 옵저버를 가진다. 이 옵저버는 두 가지를 체크한다.
currentLevel
값이 허용된 thresholdLevel
보다 크다면, 프로퍼티 옵저버는 currentLevel
을 thresholdLevel
로 맞춘다.currentLevel
값이 아무 AudioChannel
인스턴스로부터 받은 값보다 크다면, 프로퍼티 옵저버는 새로운 currentLevel
값을 maxInputLevelForAllChannels
타입 프로퍼티에 저장한다.처음 두 타입 프로퍼티를 체크할 때,
didSet
옵저버는currentLevel
에 기본 값을 설정한다.
AudioChannel
구조체를 새로운 두 오디오 채널을 만드는 데 사용할 수 있다.
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
만약 leftChannel
의 currentLevel
을 7로 설정한다면, maxInputLevelForAllChannels
타입 프로퍼티는 7로 수정된다.
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
만약 currentLevel
에 11을 넣으려 한다면, rightChannel
의 currentLevel
프로퍼티는 최댓값인 10으로 정해진다. maxInputLevelForAllChannels
프로퍼티 역시 10으로 수정된다.
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"