티스토리 뷰

728x90

프로퍼티 옵저버 (Property Observers)

 

프로퍼티 옵저버는 프로퍼티의 값이 변경될 때 감지하고, 반응 합니다.

매번 값이 set(설정) 될때 불려지며, 새로운 값이 현재 값과 같더라도 불려집니다. 

 

아래 위치에 프로퍼티 옵저버를 추가할 수 있습니다. 

  • 저장 프로퍼티를 정의할 때 
  • 저장 프로퍼티를 상속할 때 
  • 연산 프로퍼티를 상속할 때 

서브클래스의 프로퍼티에 옵저버를 정의할 수 있습니다. 

연산 프로퍼티setter의 값 변화를 감지할 수 있기 때문에 옵저버를 정의할 필요가 없습니다. 

 

프로퍼티에서 아래 옵저버를 하나 또는 둘다 사용할 수 있습니다. 

 

  • willSet: 값이 저장되기 전에 호출됩니다.
  • didSet: 새로운 값이 저장된 후에 바로 호출 됩니다.

 

willSet

willSet 에서, 새 프로퍼티 값의 파라미터명을 아래와 같이 지정할 수 있는데 (newTotalSteps), 

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {  // newTotalSteps로 파라미터명 지정
            print("About to set totalSteps to \(newTotalSteps)")
        }
    }
}

 

지정하지 않으면 기본값 newValue로 사용할 수 있습니다. 

class StepCounter {
    var totalSteps: Int = 0 {
        willSet {   // 파라미터명을 지정하지 않아 newValue로 사용 
            print("About to set totalSteps to \(newValue)")
        }
    }
}

 

 

didSet

 

didSet 에서도 비슷하게, 파라미터 이름을 지정하지 않으면, oldValue로 사용할 수 있습니다.

 

파라미터명 지정

class StepCounter {
    var totalSteps: Int = 0 {
        didSet(oldTotalSteps) {  // oldTotalSteps로 파라미터명 지정
            if totalSteps > oldTotalSteps {
                print("Added \(totalSteps - oldTotalSteps) steps")
            }
        }
    }
}

 

파라미터명 X

class StepCounter {
    var totalSteps: Int = 0 {
        didSet { // 파라미터명 지정하지 않아 기본값 oldValue로 사용
            if totalSteps > oldValue {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}

 

 

아래는 willSetdidSet을 둘다 사용한 예제 입니다. 

값이 변하기 전과 변하고 나서 호출되는 순서를 확인할 수 있습니다. 

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            print("oldValue:", oldValue)
        }
    }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// oldValue: 0
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// oldValue: 200
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// oldValue: 360

 

NOTE

만약 in-out 파라미터로 선언된 함수의 인자에 프로퍼티를 넘기면 willSet과 didSet이 항상 실행됩니다.
in-out 파라미터는 프로퍼티가 항상 복사 되기 때문에 원래 값에 새 값을 덮어 쓰게 됩니다.

 

 


전역변수와 지역변수 (Global and Local Variables)

 

연산 프로퍼티와 프로퍼티 옵저버 기능은 전역변수지역변수 모두에서 사용 가능합니다. 

 

전역 변수 - 메소드, 클로저, 타입 컨텍스트 외부에 정의된 변수

지역 변수 - 메소드, 클로저, 타입 컨텍스트 안에 정의된 변수 

 

NOTE

전역 상수/ 변수
Lazy Stored Properties 와 같이 지연 연산 됩니다. 
하지만 lazy 키워드가 필요 없습니다. 

반면, 지역 상수/ 변수는 지연 연산될 수 없습니다.

 

 


 

타입 프로퍼티 (Type Properties)

 

 여기에 정리 했으나, 공식문서를 보고 한번더 정리하겠습니다.

 

 

타입프로퍼티는 특정 타입에 속한 프로퍼티로 그 타입에 해당하는 단 하나의 프로퍼티만 생성됩니다. 

모든 인스턴스에 공통으로 사용되는 값을 정의할때 유용합니다.

 

NOTE

타입프로퍼티는 항상 초기값을 지정해서 사용해야 합니다.
타입 자체에는 초기자(Initializer)가 없어 초기화 할 곳이 없기 때문!!

 

타입 프로퍼티 구문 (Type Property Syntax)

 

타입프로퍼티를 정의 할때 static 키워드를 사용합니다. 

클래스 타입에 대한 연산 타입 프로퍼티의 경우 class 키워드를 사용하여 하위클래스가 상위클래스의 구현을 재정의 (override)할 수 있습니다.

 

아래는 구조체, 열거형, 클래스의 타입프로퍼티에 대한 구문을 보여주는 예 입니다.

 

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
    }
}

 

NOTE

위의 예제처럼 연산 프로퍼티는 읽기 전용으로 사용할 수도 있지만, 
읽고 쓰는 프로퍼티로도 사용할 수 있습니다.

 

 

 

타입 프로퍼티의 접근과 설정 (Querying and Setting Type Properties)

 

타입프로퍼티도 점 구문(.)으로 프로퍼티 값을 할당하거나, 가져올 수 있습니다.

 

print(SomeStructure.storedTypeProperty)             // Some value
SomeStructure.storedTypeProperty = "Another value"
print(SomeStructure.storedTypeProperty)             // Another value
print(SomeEnumeration.computedTypeProperty)         // 6
print(SomeClass.overrideableComputedTypeProperty)   // 107

 

 

아래는 오디오 채널 볼륨을 조절하고 관리하는 구조체로 두개의 저장 타입프로퍼티를 사용한 예 입니다. 

 

오디오 채널

 

  • 오디오 채널의 레벨이 0인 경우 표시등은 켜지지 않습니다.
  • 오디오 채널의 레벨이 10인 경우 모든 표시등이 켜집니다.
  • 위 그림에서 왼쪽 오디오 채널 레벨은 9, 오른쪽 채널 레벨은 7 입니다.

  • 아래는 채널 레벨이 10을 초과하지 않게 제한하고,
    10이하의 레벨이면 해당 값을 할당해주는 예제 입니다.
struct AudioChannel {
    static let maxLevel = 10
    static var inputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.maxLevel {
                currentLevel = AudioChannel.maxLevel
            }
            if currentLevel > AudioChannel.inputLevelForAllChannels {
                AudioChannel.inputLevelForAllChannels = currentLevel
            }
        }
    }
}

var leftChannel = AudioChannel()              // 왼쪽 채널
var rightChannel = AudioChannel()             // 오른쪽 채널

// 1
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)               // 7
print(AudioChannel.inputLevelForAllChannels)  // 7

// 2
rightChannel.currentLevel = 300
print(rightChannel.currentLevel)              // 10
print(AudioChannel.inputLevelForAllChannels)  // 10

 

코드 풀이 

더보기

1.

  • leftChannel의 currentLevel에 7을 할당 합니다.
  • didSet 부분에 첫번째 조건문에 만족하지 않으므로 통과
if currentLevel (7) > AudioChannel.maxLevel (10) {  // false
     currentLevel = AudioChannel.maxLevel  
}
  • 두번째 조건문에 만족
  if currentLevel (7) > AudioChannel.inputLevelForAllChannels (10) {
       AudioChannel.inputLevelForAllChannels = currentLevel  
       // AudioChannel.inputLevelForAllChannels = 7
  }

 

2. 

  • rightChannel의 currentLevel에 300을 할당 합니다.
  • didSet 부분에 첫번째 조건문에 만족
if currentLevel (300) > AudioChannel.maxLevel (10) {
    currentLevel = AudioChannel.maxLevel
    // currentLevel = 10
}

 

다음은 

Property Wrapper에 대해 블로깅 해보겠습니다!

 

728x90