프로퍼티를 통해 특정 값을 클래스, 구조체, 열거형에 넣을 수 있다. 저장 프로퍼티(stored properties
)는 인스턴스의 일부로서 변수나 상수 값을 저장하고, 연산 프로퍼티(computed properties
)는 특정 값을 연산한다. 열거형은 저장 프로퍼티 기능을 지원하지 않는다.
저장 및 연산 프로퍼티 이외에도 타입 프로퍼티(type properties
)가 존재한다. 인스턴스가 아니라 타입 그 자체 값을 가지고 있다는 데 주의.
프로퍼티 옵저버(property observers
)를 통해 프로퍼티 값이 바뀌면 감지할 수도 있다. 또는 프로퍼티 래퍼(property wrapper
)로 여러 개의 프로퍼티 값을 세팅하거나 가져올 수도 있다. 이 기능은 물론 사용자 편의대로 커스텀할 수 있다.
저장 프로퍼티는 클래스나 구조체 인스턴스 내 저장된 변수 및 상수로 표현된 값을 저장한다.
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
이니셜라이저를 통해 firstValue
값을 처음에는 0으로, 이후에는 .firstValue = 6
을 통해 직접 값을 수정할 수도 있다.
저장 프로퍼티 중 상수에 이니셜라이저로 값을 설정한다면 이후 변경할 수 없다.
.
연산자를 통해 직접 프로퍼티에 접근 가능하지만, 일반적으로 안전성을 위해setter
로 설정하자.
구조체 인스턴스를 생성하고 그 인스턴스를 상수에 할당했다고 가정하자. 이때 저장 프로퍼티 타입이 변수라 할지라도 인스턴스 프로퍼티를 변경할 수 없다. 인스턴스 타입 자체가 상수이기 때문이다.
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
반면 값 타입인 구조체가 아니라 참조 타입인 클래스는 다르다. 클래스를 상수에 할당하더라도 그 인스턴스의 변수로 선언된 저장 프로퍼티 값을 수정할 수 있다.
레이지 저장 프로퍼티(lazy stored properties
)는 말 그대로 초깃값이 처음 사용되기 전까지는 정해지지 않은 프로퍼티를 일컫는다. 프로퍼티의 성격 자체가 게으른
프로퍼티이기 때문에 처음 사용하기 전까지는 미정이라고 생각하자. 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 hasn't yet been created
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
DataManager
클래스의 importer
는 DataImporter()
클래스를 할당 레이지 저장 프로퍼티다. DataImporter
가 상당한 연산량을 먹는다고 가정한 사용자 입장에서 importer
를 레이지로 선언함으로써 연산을 뒤로 미룰 수 있다.
즉 DataManager
클래스는 필요할 때 DataImporter
를 생성해 사용하기 때문에 lazt
를 사용했다. 위 코드에서 DataImporter
가 처음 생성되는 부분은 filename
이라는, DataImporter
클래스의 프로퍼티 값을 얻어내는 순간이다.
lazy
로 선언된 프로퍼티가 멀티 스레드에서 동시에 접근되고 그 프로퍼티가 아직까지 초기화되지 않았다면, 여러 번 초기화될 수도 있다.
저장 프로퍼티뿐만 아니라, 인스턴스 변수를 통해서도 프로퍼티가 담고 있는 값을 저장할 수 있다.
클래스, 구조체, 열거형은 연산 프로퍼티를 통해 이 데이터 타입의 프로퍼티 값을 가져오거나 설정할 수 있다. 이때 .
연산자를 통해 직접적으로 조작하는 게 아니라 간접적으로 조작한다는 점에 주의하자.
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
// initialSquareCenter is at (5.0, 5.0)
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)"
get
프로퍼티를 통해 Point
구조체로 선언된 센터 값을 리턴하고, set
프로퍼티를 통해 newCenter
값을 설정하고 있다.
게터로 리턴할 값을 한 줄로 쓸 수도 있다.
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)
}
}
}
origin
프로퍼티와 size
프로퍼티는 이미 값을 가지고 있는 저장 프로퍼티다. 세터는 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 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"
세터가 없기 때문에 처음에 이니셜라이저로 준 값을 각각 너비, 높이, 깊이로 받은 Cuboid
구조체의 volume
프로퍼티는 주어진 저장 프로퍼티 값을 활용해 부피를 계산, 리턴하는 연산 프로퍼티다.