이번 게시글에서는 프로퍼티 관찰자와 프로퍼티 래퍼, 타입프로퍼티에 대해서 설명할 것이다.
property observer라고도 한다.
프로퍼티의 데이터가 수정되는것을 관찰하고 수정 전후의 시점에 코드를 실행 할 수 있다.
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("다음 숫자로 총 걸음이 업데이트 됩니다. \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("다음 만큼 걸음이 추가되었습니다. \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
stepCounter.totalSteps = 360
stepCounter.totalSteps = 896
//결과: 다음 숫자로 총 걸음이 업데이트 됩니다. 200
//결과: 다음 만큼 걸음이 추가되었습니다. 200 steps
//결과: 다음 숫자로 총 걸음이 업데이트 됩니다. 360
//결과: 다음 만큼 걸음이 추가되었습니다. 160 steps
//결과: 다음 숫자로 총 걸음이 업데이트 됩니다. 896
//결과: 다음 만큼 걸음이 추가되었습니다. 536 steps
데이터의 접근 이전 변경될 데이터 값 = willSet
변경되기 이전의 데이터 값 = oldSet
didSet은 파라메터 이름을 지원하지 않고 무조건 oldSet키워드로만 접근이 가능하다.
willSet의 파라메터 이름은 생략이 가능하다.
()소괄호를 지우고 newValue 키워드로 접근가능하다
관찰자로 데이터의 접근을 관리하면 데이터의 접근 전후의 시점으로 기록이 가능하다.
지금이야 print() 만 하지만 데이터 log을 DB에 저장한다던가 할 수 있다.
위의 계산 프로퍼티의 경우 set/get 키워드를 이용한 프로퍼티에 접근할때만 데이터 처리가 가능했다.
이를 모든 저장 프로퍼티에 사용하려면 동일한 기능이라 하더라도 모든 저장 프로퍼티를 계산프로퍼티로 바꿔야 한다는 단점이 있다
이를 해결하기 위해 프로퍼티 래퍼 property wrapper가 있다.
사용법은 저장 프로퍼티 앞에 @wrapperName을 붙이는 형식으로 사용하면 된다. 자세히 알아보자
@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)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
위 코드는 저장프로퍼티에 값이 12를 초과하지 않는 값이 저장되도록 하는 프로퍼티 래퍼를 만든 코드이다.
@TwelveOrLess를 사용하지 않고 명시적으로 사용하는 방법도 있다 아래 코드를 보자
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
다음과 같이 = 를 이용해 명시적으로 래퍼를 적용할 수 있다.
위 코드에서 프로퍼티 래퍼로 래핑한 프로퍼티는 초기값을 설정 할 수 없다
struct SmallRectangle {
@TwelveOrLess var height: Int = 10 // 오류발생
var width = TwelveOrLess() //여기엔 값 넣을 자리가 없음
}
다음과 같은 상황에서 초기값을 어떻게 넣을 수 있을까?
생성자를 이용하면 된다.
정확히는 프로퍼티래퍼에 생성자를 만들어 주면 된다.
@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)
}
}
↳ 인수 개수별로 총 3개의 생성자를 만들었다.
//기본 생성자 호출
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
↳ 아무 인수없이 이전처럼 프로퍼티 래퍼를 호출, 기본생성자 호출
//프로퍼티 대입 형식으로 생성자 호출
struct UnitRectangle {
//정상작동, 대입한 값이 자동으로 프로퍼티 래퍼의 생성자로 연결됨
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
↳ 인수를 한개만 사용, 프로퍼티에 대입 형식으로 사용
struct NarrowRectangle {
//다음과 같이 프로퍼티 래퍼에 소괄호()를 사용해서 바로 생성자를 호출 할 수도 있다.
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"
↳ 프로퍼티 래퍼에 소괄호를 이용해서 생성자 호출
//위 두가지 형태를 섞어서 사용할 수 도 있다.
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"
↳ 두개의 형태를 섞어서 사용
func someFunction() {
@SmallNumber var myNumber: Int = 0
myNumber = 10
// now myNumber is 10
myNumber = 24
// now myNumber is 12
}
그리고 프로퍼티 래퍼는 지역변수와 전역변수에서 사용가능하다
↳ 클래스/구조체의 프로퍼티가 아니라 변수에서도 사용이 가능하다!
타입프로퍼티란? 다른 언어들에서의 static키워드를 사용한 프로퍼티를 말한다 인스턴스 생성없이 바로 사용할 수 있는 프로퍼티를 말한다.
풀어서 설명하면 <클래스와구조체>에서 잠깐 이야기 했는데 클래스와 구조체는 타입이라고 한적이 있다.
실제로 자주 사용하는 Int, Double, Bool, String 타입들이 struct로 작성되어있다.
타입프로퍼티와 타입 메소드는 이런 타입에 붙어있는 존재들이라고 생각하면 된다.
↳ 마치 이런 핸드폰에 달고다니는 젠더키링처럼 타입에 붙어 다니면서 가방에서 꺼내지 않아도(= 인스턴스를 생성하지 않아도) 간단하게 바로바로 사용할 수 있는 존재들이 타입 프로퍼티와 타입메소드들이다.
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
//구조체, 타입프로퍼티
print(SomeStructure.storedTypeProperty)
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Some value."
// Prints "Another value."
//열거형, 타입프로퍼티
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
//클래스, 티입 프로퍼티
print(SomeClass.computedTypeProperty)
// Prints "27"
타입 프로퍼티를 사용하면 인스턴스 생성없이 내부의 프로퍼티들의 접근이 가능해 진다.