10-2. 프로퍼티 관찰자(property observer), 프로퍼티 래퍼

프로퍼티 관찰자는 프로퍼티 값이 변경되는지 관찰하고 응답한다. 이는 현재 값이 새로운 값과 같아도 값이 설정되는 시기에 호출된다. 다음의 경우에 관찰자 추가가 가능하다.

  • 정의한 저장된 프로퍼티
  • 상속 저장된 프로퍼티
  • 상속 계산된 프로퍼티

프로퍼티 관찰자

  1. 프로퍼티 관찰자 정의는 2가지 선택사항이 있으며, willSet과 didSet이 있다.
  • willSet은 값이 저장되기 직전에 호출된다. 상수 파라미터로 새로운 프로퍼티 값이 전달된다. willSet 구현의 일부로 이 파라미터에 새로운 이름을 가질 수 있다. 파라미터명과 구현 시 소괄호를 작성하지 않으면 newValue의 기본 파라미터로 만들어진다
  • didSet은 값이 저장되자마자 호출된다. 예전 프로퍼티값을 포함한 상수 파라미터가 전달된다. 파라미터명을 사용하거나 oldValue인 기본파라미터 사용이 가능하다. didSet관찰자 프로퍼티 값을 할당하게 되면 새로운 값으로 방금 설정한 값을 대체한다.
  • 수퍼클래스 프로퍼티의 will, didSet 관찰자는 수퍼클래스 초기화가 호출된 후 하위클래스에서 프로퍼티가 설정될 때 호출된다. 수퍼클래스 초기화가 호출되기 전 클래스 자체 프로퍼티를 설정하는 동안 호출되지 않는다.
class StepCounter {
	var totalStep : Int = 0 {
    	willSet(newTotalSteps) {
        	print("\(newTotalSteps)")
        } didSet {
        	if totalSteps > oldValue {
            	print("\(totalSteps - oldValue)")
            }
        }
    }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 200
//윌셋에서 200 출력, 디드셋에서 200(200-0)출력한다.
stepCounter.totalSteps = 360
//윌셋에서 360, 디드셋에서 160(360-200)을 출력한다.
//바뀌기 직전과 직후를 출력하므로 둘다 출력이 되고, 꼭 둘다 쌍으로 선언할 필요는 없다.
//oldValue를 통해서 디드셋에서 바뀌기 전 값 접근이 가능하다는게 포인트다.
  1. 관찰자를 가진 프로퍼티를 in-out파라미터로 함수에 전달하면 관찰자는 항상 호출이 된다. 함수에서 변경되는 변수의 상태를 실시간으로 파악하며 관찰 기능이 가능하다는 것이다.

프로퍼티 래퍼

  1. 프로퍼티 래퍼는 프로퍼티가 저장되는 방법을 관리하는 코드와 프로퍼티를 정의하는 코드사이에 분리계층을 추가한다. 프로퍼티 래퍼를 정의하기 위해서 wrappedValue 프로퍼티를 정의한 구조체, 열거형 또는 클래스를 만든다.
@propertyWrapper
struct TwelveOrLess {
	private var number = 0
    var wrappedValue : Int {
    	get {
        	return number
        }
        set {
        	number = min(newValue, 12)
        }
    }
    
    //게터는 저장된 값을 반환하고, 세터는 새로운 값이 12보다 작거나 같은걸 확인하고 반환한다. 
    //프로퍼티 래퍼는 wrappedValue를 프로퍼티의 값으로 반환해주게 된다.
  1. 다음 예시는 프로퍼티 래퍼를 활용한 예시를 들고 있다. 프로퍼티 래퍼는 프로퍼티에게 일정한 특성을 부여하는 키워드라고 생각하면 좋다.
struct SmallRectangle {
	@TwelveOrLess var height : Int
    @TwelveOrLess var width : Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
//0을 출력한다.

rectangle.height = 10
//새로운 값을 넣어줬으니 setter가 작용할 것이다.
print(rectangle.height)
//아직 12보다 작으므로 10을 출력한다.

rectangle.height = 24
//세터가 작용하고 12보다 크므로 12를 출력한다.
  1. 래핑된 프로퍼티를 위한 초기값 설정은 프로퍼티 래퍼를 직접 초기화 해주어야 설정이 가능하다.
@propertyWrapper
struct SmallNumber {
	private var maximum : Int
    private var number : Int
    
    var wrappedValue : Int {
    	get { return number }
        set { number = min(newValue, maximun) }
//여기에서 보면 현재 구조체 내 프로퍼티에 값이 하나도 정의되지 않은 상태이다.   
	init() {
    	maximum = 12
        number = 0
        
    init(wrappedValue : Int) {
    	maximun = 12
        number = min(wrappedValue, maximun)
    }
    
    init(wrappedValue : Int, maximum : Int) {
    	self.maximum = maximum
   	    number = min(wrappedValue, maximun)
    }
    //기본적으로 3개의 초기화를 지원한다. 
  1. 프로퍼티 래퍼에 대한 초기화 유형에 따라서 초기화 방식이 다 다르다. 다음에서 확인가능하다.
struct ZeroRectangle {
	@SmallNumber var height : Int
    @SmallNumber var width : Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
//0, 0을 출력한다. 가장 첫 init() 이 호출되기 때문이다.

struct UnitRectangle {
	@SmallNumber var height : Int = 1
    @SmallNumber var width : Int = 1
}

var unitRectangle = UnitRectngle()
print(unitRectangle.height, unitRectangle.width)
//1, 1을 출력한다. 초기값을 설정해줘도 init()을 호출한다. 
//초기값으로 wrappedValue를 전달하지 않았기에 그렇다.

struct NarrowRectangle {
	@SmallNumber(wrappedValue : 2, maximun : 5) var height : Int
    @SmallNumber(wrappedValue : 3, maximun : 4) var width : Int
}

var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
//2, 3을 출력한다. 다음에서는 래핑한 값을 통해서 초기화가 가능하다. 
//위의 예시에서 3번에 해당하는 초기화를 사용하게 된다.
//init(wrappedValue : Int, maximun : Int)

좋은 웹페이지 즐겨찾기