[Swift] 스위프트에서 KVO 사용하기
안녕하세요, iOS 개발하는 루피입니다.
오늘은 공식문서를 바탕으로 KVO(Key-Value Observing) 디자인 패턴에 대해 정리해 보겠습니다.
바로 시작하겠습니다.
한 객체의 속성 변경 사항을 다른 객체들에게 알립니다.
공식문서 제일 상단에 쓰여있는 문구입니다.
다른 객체의 속성을 지켜보고 있다가. "어 바뀌었네?" 하고 다른 객체에게 "야!! 저기 바뀌었어"라고 말해주는 거 같습니다. RxSwift로 프로젝트를 진행하면서, 배운 구독하고 있다가 변화가 감지되면 반응한다는 개념과 유사한 거 같은데요??
맞습니다. KVO 는 코코아 프레임 워크에서 제공하는 기본적인 관찰 메커니즘으로, Objective-C 런타임을 기반으로 동작합니다. 따라서 이와 유사하게 옵저버 디자인 패턴 방식을 채택해 써드 파티로는 RxSwift나 퍼스트 파티로 Combine과 같은 라이브러리와 프레임워크가 만들어졌다고 합니다.
Overview
Key-Value Observing(KVO)는 Cocoa 프로그래밍 패턴으로, 객체의 속성 변경 사항을 다른 개체에 알리는 데 사용됩니다. 이 패턴은 앱의 모델과 뷰처럼 논리적으로 분리된 부분 간에 변경 사항을 전달하는 데 유용합니다.
예를 들어, 모델 데이터가 변경되면 이를 관찰하는 뷰가 자동으로 업데이트하는데 사용할 수 있습니다.
KVO는 NSObject를 상속받은 클래스에서만 사용할 수 있습니다.
KVO는 Objective-C 런타임을 활용하기 때문에, 관찰 대상 클래스는 반드시 NSObject를 상속받아야 합니다.
그러기에, Struct나 Enum에서는 사용할 수 없습니다.
- Swift Struct 와 Enum은 Objective-C 런타임과 호환되지 않습니다.
- strcut 와 Enum은 클래스가 아니므로 상속 개념이 없기에 NSObject를 상속받을 수 없습니다.
- 동적 dynamic dispatch를 지원하지 않고, static dispatch를 기본적으로 사용합니다.
Strcut나 Enum에서도 상태 변화를 감지하고 싶은데...
- willSet, didSet 사용합니다.
- Strcut 내부에서 속성 변경 시 동작을 정의할 수는 있지만 외부 객체에서 관찰할 수는 없습니다.
- Combine 사용합니다.
- RxSwift를 사용합니다.
Annotate a Property for KVO
KVO를 통해 관찰하려는 속성은 @objc 속성과 dynamic 수정자를 함께 사용해 표시해야 합니다.
- @objc : 속성을 Objective-C 런타임에서 접근 가능하도록 설정합니다.
- dynamic : 속성의 변경 사항을 컴파일 타임이 아닌 런타임에 동적으로 처리할 수 있도록 합니다.
아래 예제는 MyObjectToObserve 클래스와 관찰 가능한 속성인 myDate를 정의한 코드입니다
class MyObjectToObserve: NSObject {
@objc dynamic var myDate = NSDate(timeIntervalSince1970: 0) // 1970
func updateDate() {
myDate = myDate.addingTimeInterval(Double(2 << 30)) // Adds about 68 years.
}
}
Define an Observer
관찰자 클래스의 인스턴스는 하나 이상의 속성에서 발생한 변경 사항에 대한 정보를 관리합니다.
관찰자를 생성할 때, 관찰하고자 하는 속성에 해당하는 key path를 지정하여 observe(_:options:changeHandler:) 메서드를 호출함으로써 관찰을 시작합니다.
아래 예제에서는 \.objectToObserve.myDate key path가 MyObjectToObserve 클래스의 myDate 속성을 참조합니다.
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("myDate changed from: \(change.oldValue!), updated to: \(change.newValue!)")
}
}
}
위 코드에서, `NSKeyValueObservedChange` 인스턴스의 `oldValue`와 `newValue` 속성을 사용하여 관찰 중인 속성에서 어떤 변화가 있었는지 확인할 수 있습니다.
만약 속성이 어떻게 변경되었는지 알 필요가 없다면, `options` 매개변수를 생략할 수 있습니다. 이 경우, 새로운 값과 이전 값을 저장하지 않으므로 `oldValue`와 `newValue` 속성은 nil이 됩니다. 하지만 그만큼 메모리 사용량은 줄어들겠죠??
Associate the Observer with the Property to Observe
관찰하려는 속석을 관찰자와 연결하려면, 관찰 대상 객체를 관찰자의 초기화 메서드에 전달하면 됩니다.
let observed = MyObjectToObserve()
let observer = MyObserver(object: observed)
Respond to a Property Change
KVO를 사용하는 객체(observed)는 속성 변경 사항을 자동으로 관찰자에게 알립니다. 아래 예제에서는 updateData 메서드를 호출하여 myDate 속성을 변경합니다. 이 호출은 자동으로 관찰자의 변경 핸들러를 실행합니다.
observed.updateDate() // 관찰자의 변경 핸들러가 실행됩니다.
// Prints "myDate changed from: 1970-01-01 00:00:00 +0000, updated to: 2038-01-19 03:14:08 +0000"
위 예제에서는 속성 변경에 반응하여 날짜의 new 값과 old값을 출력합니다.
KVO의 한계
메모리 관리 문제
KVO를 사용할 때 관찰자를 명시적으로 해제하지 않으면 메모리 누수나 충돌이 발생할 수 있습니다.
특정 속성만 관찰 가능
KVO는 NSObject를 상속받은 클래스에서만 사용가능합니다. 따라서 Swift-native 타입이나 Swift 클래스에서는 사용할 수 없습니다.
이외에도 더 공부해보면 좋을거 같습니다!
오늘도 화이팅입니다!