
프로퍼티는 클래스, 구조체, 열거형과 관련된 값을 뜻함
3가지 종류가 존재
단순히 값을 저장하고 있는 프로퍼티
struct Point {
let name: String
var x, y: Int
// name, x, y는 저장 프로퍼티
}
class Person {
let name: String
var age: Int
// name, age는 저장 프로퍼티
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
구조체를 상수(let)로 선언한 경우 그 인스턴스의 프로퍼티를 변경할 수 없다.
하지만 클래스는 가능, 참조 타입이기 때문
let point1 = Point(name: "p1", x: 10, y: 3)
point1.x = 3 // Compile Error -> 'point1' is a 'let' constant
var point2 = Point(name: "p2", x: -5, y: 4)
point2.x = 3 // point2.x : 3
let kim = Person(name: "Kim", age: 23)
kim.age += 1 // kim.age : 24
var lee = Person(name: "Lee", age: 25)
lee.age += 1 // lee.age : 26
lazylazy 키워드로 지연 저장 프로퍼티를 선언할 수 있다.
값이 처음으로 사용되기 전에는 계산되지 않는 프로퍼티
사용
프로퍼티가 특정 요소에 의존적이어서 해당 요소가 끝나기 전에 값을 알지 못할 때
복잡한 계산이나 부하가 많이 걸리는 작업을 인스턴스 초기화 시점에서 피하고 싶을 때
무조건 var로 선언해야 한다.
상수는 초기화가 되기 전에 값을 가져야 하는데,
지연 저장 프로퍼티는 처음 실행되기 전까지는 값을 갖지 않기 때문
스레드에 따른 초기화 횟수
단일 스레드에서만 지연 프로퍼티가 실행되면 초기화는 한 번만 하지만,
여러 스레드에서 사용되면 한 번만 한다고 보장하지 못한다.
class DiskReader {
// 디스크에서 파일을 읽어오는 작업
// 초기화하는데에 시간이 오래 걸린다고 가정
func readDisk() {
...
}
}
...
class FileManager {
lazy var reader = DiskReader()
var file = [String]()
}
let fileManager = FileManager()
fileManager.file.append("readme.txt")
fileManager.file.append("SceneDelegate.swift")
// 이 시점엔 FileManager의 DiskReader 인스턴스가 생성되어 있지 않음
fileManager.reader.readDisk()
// 이 때 DiskReader 인스턴스를 생성
Computed Property는 실제 값을 저장하지 않고 getter와 setter를 제공하여
값을 가져오거나 간접적으로 다른 프로퍼티의 값을 설정할 수 있다.
...
var computedProperty: Type {
get { }
set(parameter = "newValue") { }
}
...
get
해당 프로퍼티를 읽을 때 get의 코드 블럭을 실행하게 된다.
이 코드 블럭은 프로퍼티 타입의 반환값을 제공해야 하며, 한 줄로 작성할 때는 return 키워드 생략 가능
무조건 get은 포함해야 한다
set
해당 프로퍼티에 값을 쓸 때 set의 코드 블럭을 실행하게 된다.
위 형태에서 set(parameter) 형태로, 쓰여질 값이 parameter에 넘어오게 되는데
set { } 형태로 인자 없이 쓸 경우, 쓰여질 값은 코드 블럭 안에서 기본 인자 이름인 newValue로 사용된다.
set은 제공하지 않아도 되며, set을 제공하지 않으면 get-only property가 된다.

프로퍼티는 새 값이 설정될 때마다 (set될 때 마다) 이벤트를 감지할 수 있는 옵저버를 제공
서브클래스의 프로퍼티에 옵저버를 정의 가능
inout 파라미터에 프로퍼티를 넘기면 willSet, didSet이 항상 호출됨
inout 파라미터는 항상 복사가 일어나기 때문
lazy stored property: 사용 불가
computed property: 값의 변화를 감지할 수 있으므로 사용 불가
willSet | didSet | |
|---|---|---|
| 시점 | 값이 저장되기 직전에 호출됨 | 값이 저장되고 난 직후에 호출됨 |
| 기본 파라미터 이름 | newValue | oldValue |
class Person {
var phone: String {
didSet { print("Subclass's didSet observer.") } // 세 번째로 실행
willSet { print("Subclass's willSet observer.") } // 두 번째로 실행
}
init(phone: String) {
self.phone = phone
}
}
class Student: Person {
var school: String
var grade: Int = 1 {
didSet { print("last year, grade was \(oldValue)") }
willSet { print("this year, grade is \(newValue)") }
}
// 서브클래스의 프로퍼티 옵저버
override var phone: String {
didSet { print("from \(oldValue)") } // 마지막으로 실행
willSet { print("Phone number changes to \(newValue)") } // 첫 번째로 실행
}
init(phone: String, school: String, grade: Int) {
self.school = school
self.grade = grade
super.init(phone: phone)
}
}
let student = Student(phone: "01012345678", school: "Harvard", grade: 2)
student.grade = 3
// this year, grade is 3
// last year, grade was 2
student.phone = "01098765432"
// Phone number changes to 01098765432
// Subclass's willSet observer.
// Subclass's didSet observer.
// from 01012345678
static 키워드를 사용해 타입 프로퍼티로 설정
인스턴스 프로퍼티와 다르게 해당 타입(class, struct, enum)에 존재하는 단 하나의 프로퍼티
특정 타입의 모든 인스턴스에서 공통으로 사용되는 값을 정의할 때 유용
타입 자체에는 생성자가 없기 때문에 항상 초기값을 지정해주어야 한다.
struct SomeStructure {
static let storedTypeProperty = "SomeStructure's stored type property."
static var someValue = 2
static var computedTypeProperty: Int {
return 3
}
}
class SomeClass {
static let storedTypeProperty = "SomeClass's stored type property."
static var someValue = 2
static var computedTypeProperty: Int {
return 3
}
}
enum SomeEnumeration {
static let storedTypeProperty = "SomeStructure's stored type property."
static var someValue = 2
static var computedTypeProperty: Int {
return 3
}
}
print(SomeStructure.storedTypeProperty)
// SomeStructure's stored type property.
print(SomeClass.computedTypeProperty)
// 3
someEnumeration.someValue = 10
print(SomeEnumeration.someValue)
// 10
프로퍼티가 저장되는 방법을 관리하는 코드와
프로퍼티를 정의하는 코드 사이에 분리 계층을 추가
wrappedValue 프로퍼티를 같이 정의@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height) // 0
rectangle.height = 10
print(rectangle.height) // 10
rectangle.height = 24
print(rectangle.height) // 12
Property Wrapper에 별다른 초기화 구문이 없다면, Wrapper을 사용하는 쪽은 Wrapping된 프로퍼티의 초기값을 그대로 따름
(위의 예제에서 SmallRectangle의 height, width는 Wrapping된 프로퍼티인 number의 초기값 0을 가짐)
@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)
}
}
init()struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
// height, width = 0
초기값을 설정하지 않으면 Property Wrapper의 init()이 실행
init(wrappedValue:)struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
// height, width = 1
= 1은 init(wrappedValue:) 초기화 호출에 전달
SmallNumber(wrappedValue: 1) 호출로 인스턴스 생성init(wrappedValue:maximum:)struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
// or
// @SmallNumber(maximum: 5) var height: Int = 2
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
// or
// @SmallNumber(maximum: 4) var height: Int = 3
}
var narrowRectangle = NarrowRectangle()
// height = 2, width = 3
narrowRectangle.height = 100
narrowRectangle.width = 100
// height = 5, width = 4
Property Wrapper 사용 시 커스텀 이니셜라이저를 사용 가능
Wrapping된 값 외에도 투영된 값(Projected Value)에 의해 추가적인 기능을 노출 가능
$ 표시를 붙여 사용