Properties associate values with a particular class, structure, or enumeration. Stored properties store constant and variable values as part of an instance, whereas computed properties calculate (rather than store) a value. Computed properties are provided by classes, structures, and enumerations. Stored properties are provided only by classes and structures.
프로퍼티 (properties)는 값을 특별한 클래스, 구조체, 또는 열거체와 결합합니다. 저장 프로퍼티는 인스턴스의 일부분으로써 상수와 변수 값을 저장하는 반면, 연산 프로퍼티는 값을 (저장한다기 보단) 계산합니다. 연산 프로퍼티는 클래스, 구조체, 및 열거체에서 제공합니다. 저장 프로퍼티는 클래스와 구조체에서만 제공합니다.
지금까지 클래스와 구조체 내에서 선언했던 변수와 상수가 바로 프로퍼티이다.
하지만 정확한 표현은 아닌것이, Swift내 프로퍼티는 3가지 종류의 프로퍼티가 있다. 사실 이거말고 더 있긴 한거 같은데 일단
1. Stored Property: 저장 프로퍼티
2. Computed Property: 연산 프로퍼티
3. Type Properties: 타입 프로퍼티
저장 프로퍼티와 연산 프로퍼티는 타입의 인스턴스에 결합되어 사용되지만, 타입 프로퍼티는 타입 그 자체와 결합되어 사용된다.
이 외에도 프로퍼티 관찰자 (property observers) 를 정의할 수 있다. 이는 이름 그대로 프로퍼티 값의 변화를 관찰 하는 것으로, “저장 프로퍼티”에 추가할 수 있다. 새 값의 속성이 현재 값과 동일하더라도, 속성 값이 설정되면 호출된다.
프로퍼티 포장 (property wrapper) 을 사용하여 여러 속성에서 획득자 (getter) 와 설정자 (setter) 코드를 재사용할 수도 있다.
저장 프로퍼티는 클래스와 구조체에서만 사용할 수 있고, 값을 저장하기 위해 선언되는 상수/변수를 의미한다.
즉 우리가 프로퍼티라고 알고 있던것은 정확하게 말하자면 저장 프로퍼티였던 것.
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 이 범위는 정수 값 0, 1, 2 를 나타냄
rangeOfThreeItems.firstValue = 6
// 이제 범위는 정수 값 6, 7, 8 을 나타냄
프로퍼티가 호출되기 전까지는 선언만 될 뿐 초기화되지 않고 있다가, 프로퍼티가 호출되는 순간에 초기화 되는 저장 프로퍼티이다.
원래 기본적으로 모든 프로퍼티는 이니셜라이저가 선언되는 순간 모두 초기화 되어야 하는데, 이 지연 저장 프로퍼티는 이니셜라이저가 불릴 때 초기화가 되어있지 않더라도 오류가 나지 않는다.
바로 코드가 이 값에 접근할 때 초기화가 진행된다.
이러한 특성 때문에 지연 저장 프로퍼티는 무조건 변수로만 선언이 가능하다. 그 이유는 이제 말하지 않아도 알 것이다.
지연 저장 프로퍼티는 필요할 때만 초기화를 시킬 수 있는 장점이 있기 때문에 많은 인스턴스를 만들더라도 메모리를 효율적으로 관리할 수 있는 장점이 있다.
class DataImporter {
/*
DataImporter 는 외부 파일에서 자료를 불러오는 클래스입니다.
이 클래스의 초기화에는 유의미한 양의 시간이 걸린다고 가정합니다.
*/
var filename = "data.txt"
// DataImporter 클래스는 여기서 자료 불러오는 기능을 제공할 것임
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// DataManager 클래스는 여기서 자료 관리 기능을 제공할 것임
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// importer 속성의 DataImporter 인스턴스는 아직 생성하지 않음
연산 프로퍼티는 저장 프로퍼티와는 다르게 저장 공간을 가지지 않고, 다른 저장 프로퍼티의 값을 읽어 연산을 실행하거나, 프로퍼티로 전달받은 값을 다른 프로퍼티에 저장한다.
위와 같은 특성 때문에 변수로만 할당해야 한다. 다른 값을 가져와 연산하여 새로운 값을 뱉어내는 구조이기 때문.
class Person {
var nickname: String {
get { //getter (다른 저장 연산프로퍼티의 값을 얻거나 연산하여 리턴할 때 사용)
return alias
}
set(name) { //setter (다른 저장프로퍼티에 값을 저장할 때 사용)
self.alias = name
}
}
}
연산 프로퍼티는 특정 값을 저장하는 것이 아니기 때문에 타입추론이 불가능하다. 따라서 반드시 타입 어노테이션을 해줘야 한다.
선언된 프로퍼티 뒤에 대괄호{}
를 붙이는 것이 연산 프로퍼티의 사용법.
getter
는 말 그대로 “얻는” 것이다. 따라서 어떤 저장 프로퍼티의 값을 연산해서 return할 것인지. return구문이 반드시 존재해야 한다.
setter
는 말 그대로 “설정” 하는 것이다. 따라서 파라미터로 받은 값을 어떤 저장 프로퍼티에 어떻게 설정할 것인지 구현해줘여 한다.
따라서 사실 위 예제는 틀린 예제이다. 왜냐하면, get
, set
구문에서 연산 프로퍼티의 값을 가져오려하기 때문. 연산 프로퍼티는 반드시 저장 프로퍼티의 값을 가져오도록 해야 한다.
class Person {
var name: String = “wonbi”
var nickname: String {
get {
return name
}
set(name) {
self.name = name
}
}
}
class Person {
var name: String = “wonbi”
var nickname: String {
get {
return self.name + " 는 이름”
}
set(name) {
self.name = name + “는 닉네임”
}
}
}
let wonbi: Person = .init()
// get에 접근
print(wonbi.nickname) // wonbi 는 이름
// set에 접근
wonbi.nickname = “원비디”
print(sodeul.name) // 원비디는 닉네임
연산 프로퍼티인 nickname
값을 읽으면, nickname
의 get
이 실행되어 “wonbi 는 이름” 이라는 값이 나온 것이고
연산 프로퍼티인 nickname
에 값을 쓰면, “원비디”라는 값이 set
의 파라미터로 넘어가 set
함수가 실행된다.
set
함수의 매개변수는 왜 타입을 명시해주지 않을까? 그것은 이미 nickname
이라는 연산 프로퍼티를 선언 할 때 타입을 명시해주었기 때문이다.
위 예제에서는 set
의 매개변수 이름을 name으로 했지만, 다른이름으로 지어도 상관없다. 대신 단 하나의 매개변수만 존재한다는 사실을 기억하자.
get
구문 처럼 set
구문의 매개변수를 아예 생략해버릴 수도 있다. 대신 매개변수에 접근하려면 newValue
라는 이름으로 접근하면 된다. 반드시 newValue
로만 접근해야함.
get-only
로 사용할 수도 있는데 단순히 set
구문을 쓰지 않으면 된다. 더 간단하게 get
문도 삭제하고 return
만 써도 된다.
set-only
는 불가능. 무조건 get
구문을 써줘야만 한다.