간단하게 property는 member 변수이다. 그런데 Swift에는 다양한 종류의 property가 존재한다. 사실 어떻게 보면 활용방법? 테크닉에 가깝겠다. 어떻게 property를 관리하는 지 안다면, 실제 코드를 간결하고 읽기 좋게 유지할 수 있다. 그럼 출발해보자.
Property의 종류
- 역할에 따른 분류
- 인스턴스 프로퍼티
- 타입 프로퍼티
- 종류
- 저장 프로퍼티
- 연산 프로퍼티
var로만 선언 가능- 읽기 전용 구현은 가능 -
get블록을 작성 - 쓰기 전용 구현은 불가능
위의 4가지가 섞여서 존재할 수 있다. 즉 4가지의 가능성이 존재.
- 인스턴스 저장 프로퍼티
- 타입 저장 프로퍼티
- 인스턴스 연산 프로퍼티
- 타입 연산 프로퍼티
Property의 특징
- 구조체, 클래스, 열거형 내부에 구현이 가능
- 열거형에는 연산 프로퍼티만 구현 가능
- 읽기 쓰기 모두가능하게 하려면
get블럭과set블럭 모두 구현- 읽기 전용으로 만들고 싶으면
get만 구현
- 읽기 전용으로 만들고 싶으면
set에서 암시적 매개변수 newValue를 사용할 수 있음
struct Student {
// 인스턴스 저장 프로퍼티
var name: String = ""
var `class`: String = "Swift"
var koreanAge: Int = 0
// 인스턴스 연산 프로퍼티
var westernAge: Int {
get {
return koreanAge - 1
}
set(inputValue) {
koreanAge = inputValue + 1
}
}
// 타입 저장 프로퍼티
static var typeDescription: String = "학생"
// 읽기 전용 타입 연산 프로퍼티
// 읽기 전용이 default라 적지 않으면 읽기전용이라고 판단함
static var selfintroduction: String {
return "학생타입입니다."
}
}- 인스턴스 저장 프로퍼티는 일반적이니 패스
- 인스턴스 연산 프로퍼티는, 특정값을 받아서 다른 저장 인스턴스의 값을 설정해줘야 한다거나 다른 프로퍼티로 부터 연산을 통해 해당 프로퍼티의 값이 도출될 때 주로 사용
- 타입 저장 프로퍼티는 기존에 알고 있던 타입 프로퍼티
- 타입 연산 프로퍼티 역시 비슷하다.
- 이러한 방법을 사용하면 기존의 introduce와 같은 함수를 읽기 프로퍼티를 사용하여 대체하는 것도 가능
// 인스턴스 생성
var wansik: Student = Student()
wansik.name = "wansik"
wansik.koreanAge = 20
// 인스턴스 저장 프로퍼티 사용
print(wansik.name) // wansik
// 인스턴스 연산 프로퍼티 사용
print(wansik.westernAge) // 19
// 타입 저장 프로퍼티 사용
print(Student.typeDescription) // 학생
// 타입 연산 프로퍼티 사용
print(Student.selfintroduction) // "학생타입입니다."Property Observers
- 프로퍼티 감시자를 사용하면 프로퍼티 값이 변경될 때, 원하는 동작을 수행할 수 있음
- 값이 변경되기 직전에 willSet 블럭이, 변경 직후 didSet 블럭이 호출됨
- 변경되려는 값이 기존 값과 같더라도 항상 동작
willSet블럭에서 암시적 매개변수newValue를 사용 가능didSet블럭에서는oldValue를 사용 가능- 프로퍼티 감시자는 연산 프로퍼티에서 사용 불가
- 함수, 메서드, 클로저, 타입 등의 지역/전역 변수에 모두 사용 가능
struct Money {
// 프로퍼티 감시자 사용
var currencyRate: Double = 1100 {
willSet(newRate) {
print("환율이 \(currencyRate)에서 \(newRate)으로 변경될 예정입니다")
}
didSet(oldRate) {
print("환율이 \(oldRate)에서 \(currencyRate)으로 변경되었습니다")
}
}
// 매개 변수를 암시적으로 newValue, oldValue를 갖는다.
var dollar: Double = 0 {
willSet {
print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다")
}
didSet {
print("\(oldValue)달러에서 \(dollar)달러로 변경되었습니다")
}
}
// 연산 프로퍼티
var won: Double {
get {
return dollar * currencyRate
}
set {
dollar = newValue / currencyRate
}
// get, set과 함께 사용이 불가함
// willSet {
// print("원화의 값이 \(won)으로 변경될 예정입니다.")
// }
// didSet {
// print("원화의 값이 \(won)으로 변경되었습니다.")
// }
}
}
var moneyInMyPocket = Money()
// 환율이 1100.0에서 1150.0으로 변경될 예정입니다
moneyInMyPocket.currencyRate = 1150.0
// 환율이 1100.0에서 1150.0으로 변경되었습니다
//0.0달러에서 10.0달러로 변경될 예정입니다
moneyInMyPocket.won = 11500
//0.0달러에서 10.0달러로 변경되었습니다
print(moneyInMyPocket.dollar) // 10Property Wrappers
- property 저장 방법 코드와 property 관리 코드를 구분
- property set, get할 때 공통적으로 적용되는 반복코드를 관리할 수 있음
- 데이터 저장전 스레드 체크
- struct, enum, class 선언 앞에
@propertyWrapper를 붙이고wrappedValue정의하면 됨 - 특정 데이터의 boundary를 정할 수 있음
@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 } - Initializer를 통해 value 초기화 가능
@propertyWrapper struct SmallNumber { private var maximum: Int private var number: Int var wrappedValue: Int { get { return number } set { number = min(newValue, maximum) } } init() { self.maximum = 12 self.number = 0 } init(maximum: Int) { self.maximum = maximum self.number = 0 } init(wrappedValue: Int, maximum: Int) { self.maximum = maximum self.number = min(wrappedValue, maximum) } } struct ZeroRectangle { @SmallNumber(wrappedValue: 5, maximum: 10) var height: Int // custom initializer를 사용한 초기화 @SmallNumber(maximum: 10) var width: Int = 5 // 값 할당과 custom initializer를 섞어서 사용함 } - Projected Value
projectValue를 사용하면wrappedValue를 다른 방법으로 드러낼 수 있음$를 통해 접근이 가능함@propertyWrapper struct SmallNumber { private var number = 0 var projectedValue = false var wrappedValue: Int { get { return number } set { if newValue > 12 { number = 12 projectedValue = true } else { number = newValue projectedValue = false } } } } 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 // True or False || True or False } }