프로퍼티는 클래스, 구조체, 열거형과 관련된 값을 뜻함
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
lazy
lazy
키워드로 지연 저장 프로퍼티를 선언할 수 있다.
값이 처음으로 사용되기 전에는 계산되지 않는 프로퍼티
사용
프로퍼티가 특정 요소에 의존적이어서 해당 요소가 끝나기 전에 값을 알지 못할 때
복잡한 계산이나 부하가 많이 걸리는 작업을 인스턴스 초기화 시점에서 피하고 싶을 때
무조건 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)에 의해 추가적인 기능을 노출 가능
$
표시를 붙여 사용