KVO는 Key-Value Observing으로 특정 키 값의 변화를 감지하고 알려주는 코코아 프로그래밍 패턴입니다.
이 기능은 모델과 뷰처럼 앱의 논리적으로 분리되어 있는 사이 간 변경 사항을 전달하는데 유용하게 쓰입니다.
(모델과 뷰가 서로 독립적으로 작동되는 건 MVC 패턴 등 디자인 패턴에서 알 수 있음!)
이 기능은 NSObject에서 상속받은 클래스에서만 키 값을 관찰할 수 있기 때문에 Swift에서 사용하려면 @objc 어노테이션을 붙여서 사용해야 합니다.
class MyObjectToObserve: NSObject {
@objc dynamic var myDate = NSDate(timeIntervalSince1970: 0) // 1970
func updateDate() {
myDate = myDate.addingTimeInterval(Double(2 << 30)) // Adds about 68 years.
}
}
애플 공식문서에서 제공하는 예제 코드를 가져왔습니다.
Swift 런타임과 Objective-C 런타임은 다르게 동작하게 되는데 dynamic 키워드를 통해 Objective-C 런타임을 사용한 동적 디스패치를 가능하게 하고, objc 속성이 암시적으로 표시되게 합니다.
NSDate()는 Swift에서 제공하는 Date()와 동일한 기능을 하는 객체로 보면 되는데,
이 둘의 차이점이 있다면 NSDate는 NSObject를 상속받은 클래스이고, Date는 구조체로 작성되어 있습니다.
NSDate(timeIntervalSince1970: TimeInterval) -> 파라미터에 초(sec)를 넣으면 1970년 1월 1일 00:00:00 UTC 기준으로 시간을 계산해서 반환해 줍니다.
var testMyDate = NSDate(timeIntervalSince1970: 60)
print(testMyDate) // 1970-01-01 00:01:00 +0000
60초를 넣으면 이런 식으로 계산됩니다!
updateDate()에 쓰인 addingTimeInterval(TimeInterval)은 기존에 계산된 값에다가 추가적으로 초 단위를 더해서 계산해 주는 함수입니다.
var testMyDate = NSDate(timeIntervalSince1970: 60)
print(testMyDate) // 1970-01-01 00:01:00 +0000
print(testMyDate.addingTimeInterval(62)) // 1970-01-01 00:02:02 +0000
60초였던 testMyDate에다가 62초를 추가해서 2분 2초를 반환합니다.
그런데 공식 문서에서 제공하는 코드에는 Double(2 << 30) 이런 걸 사용했는데요,
이건 비트 시프트 연산자로 01010101 같은 정수 데이터의 비트를 이동시켜서 데이터 값을 변화시키는 건데 KVO를 공부하는데 지금 이 내용은 중요하지 않으니 예제 코드를 조금 바꾸어서 봅시다!
class MyObjectToObserve: NSObject {
@objc dynamic var myDate = NSDate(timeIntervalSince1970: 60)
func updateDate() {
myDate = myDate.addingTimeInterval(62)
}
}
이제 어떤 코드인지 읽히겠죠?!?
NSObject 클래스를 상속한 MyObjectToObserve 클래스의 @object, dynamic 키워드를 붙인 myDate를 KVO 방식으로 값의 변화를 감시할 수 있다는 걸 알 수 있습니다.
class MyObserver: NSObject {
@objc var objectToObserve: MyObjectToObserve
var observation: NSKeyValueObservation?
init(object: MyObjectToObserve) {
objectToObserve = object
super.init()
observation = observe( \.objectToObserve.myDate, options: [.old, .new] ) { object, change in
print("변경 전 값: \(change.oldValue!), 변경 후 값: \(change.newValue!)")
}
}
}
이제 실제로 값을 감시할 MyObserver 클래스를 만들고 observe(_ keyPath:options:changeHandler:)를 통해서 감시할 수 있습니다. myDate의 키 경로를 통해 myDate를 감시하고 있다가 값의 변경이 일어나면 변경 전의 값과 변경 후의 값을 출력해 주는 코드를 작성했습니다. ( \.objectToObserve.myDate는 MyObjectToObserve의 myDate 키 경로를 나타냅니다. )
또한 값의 변화를 알지 않아도 되면 options:를 생략해도 됩니다.
그럼 .oldValue와 .newValue의 값은 nil이 됩니다.
let myObject = MyObjectToObserve()
let myObserver = MyObserver(object: myObject)
myObject.updateDate()
// 콘솔 print
// 변경 전 값: 1970-01-01 00:01:00 +0000, 변경 후 값: 1970-01-01 00:02:02 +0000
감시하는 myObserver에 감시당할 myObject를 전달하여 둘을 연결해 주었습니다.
그리고 감시당하는 myObject의 myDate 속성의 값이 변경될 수 있도록 updateDate()를 실행하면 감시당한 결과값이 아주 잘 출력되는 것을 볼 수 있습니다.
wilSet, didSet 차이
거의 동일한 기능인 값의 변화를 감지하는 wilSet과 didSet과의 차이에 대해 궁금할 수 있는데요,
우리가 타입을 직접 만드는 경우에는 wilSet, didSet을 구현해서 값의 변화를 알 수 있겠지만 다른 사람이나 외부 라이브러리에서 정의한 타입이면 내부 소스를 마음대로 변경해서 사용할 수 없을 테니 이럴 때 KVO 방식으로 값의 변화를 관찰할 수 있다고 합니다.
출처
'DEV - iOS > Background' 카테고리의 다른 글
[iOS Interview Questions] 앱 화면의 콘텐츠를 표시하는 로직과 관리를 담당하는 객체를 무엇이라고 하는가? (0) | 2023.05.09 |
---|---|
[iOS Interview Questions] Result 타입에 대해 설명하시오. (0) | 2023.03.31 |
[iOS Interview Questions] Codable에 대하여 설명하시오. (0) | 2023.03.23 |
[iOS Interview Questions] Class의 성능을 향상 시킬수 있는 방법들을 나열해보시오. (0) | 2023.03.22 |
[iOS Interview Questions] Singleton 패턴을 활용하는 경우를 예를 들어 설명하시오. (0) | 2023.03.22 |