[RxSwift] Debugging & KVO
Debugging Compile Errors
When writting elegant RxSwift/RxCocoa code, you are probably relying heavily on the compiler to deduce types of Observables. This is one of the reasons why Swift is awesome,
but it can also be frustrating sometimes.
RxSwift/RxCocoa 코드를 작성할때, 컴파일러가 Observable의 타입을 유추하도록 크게 의존하게 됩니다. 이는 Swift가 멋진이유중 하나이지만, 때론 좌절감을 줄수도 있습니다.
(아마 타입 추론과 연관된 얘기인듯? )
RxSwift는 클로저와 제네릭을 자주사용함, 이로인해서 Swift 컴파일러가 타입추론을함으로써
복잡한타입연산에서 타입이 명확하지 않으면 컴파일러가 오류가 발생할수 있음
이러한 오류를 해결하기 위해서 타입을 명시적으로 지정하여 컴파일러가 타입추론을 명확히 할수있도록 도울필요가 있는것을 설명하는것
( 명시적 타입지정인 type annotation을 타입주석이라고 일컫는듯 함 )
images = word
.filter { $0.containsString("important") }
.flatMap { word in
return self.api.loadFlickrFeed("karate")
.catchError { error in
return just(JSON(1))
}
}
if the compiler reports that there is an error somewhere in this expression, I would suggest first annotating return types.
컴파일러가 이표현식에서 어딘가에 오류가 있다고 보고하는경우, 먼저 반환타입을 명시적으로 주석처리하는것을 추천합니다.
images = word
.filter { s -> Bool in s.containsString("important") }
.flatMap { word -> Observable<JSON> in
return self.api.loadFlickrFeed("karate")
.catchError { error -> Observable<JSON> in
return just(JSON(1))
}
}
if that doesn't work, you can continue adding more type annotation until you've localized the error
이렇게 해도 문제가 해결되지 않는다면, 더많은 타입 주석을 추가하여 오류를 지역화 할수도 있습니다.
images = word
.filter { (s: String) -> Bool in s.containsString("important") }
.flatMap { (word: String) -> Observable<JSON> in
return self.api.loadFlickrFeed("karate")
.catchError { (error: Error) -> Observable<JSON> in
return just(JSON(1))
}
}
I would suggest first annotating return types and argyments of closure
Usually, after you have fixed the error, you can remove the type annotations to clean up your code again
반환 타입과 클로저의 인수에 주석을 다는것부터 시작해보세요.
보통 오류를 해결한 후에는 코드를 정리하기 위해 타입주석을 제거 할수 있습니다.
결국 타입주석은 완성후에 지우는것이 더 좋다?
Debugging
Using debugger alone is useful, but usually using debug operator will be more efficient.debug
operator will print out all events to standard output you can add also label those events.
디버거를 사용하는것도 유용하지만, 보통 debug 연산자를 사용하는것이 더 효율적입니다.
debug 연산자는 모든 이벤트를 표준 출력에 출력하며, 이벤트에 레이블을 추가할수도 있습니다.
debug
act like a probe. Here is an example of using it:
debug 연산자는 마침 탐침 처럼 작동합니다. 아래는 debug를 사용하는 예제 입니다.
(여기서의 probe는 뭔가를 캐묻다의 의미인듯)
let subsription = myInterval(.millisecond(100))
.debug("my probe")
.map { e in
return "This is simply \(e)"
}
.subscribe(onNext: { n in
print(n)
})
Thread.sleepForTimeInterval(0.5)
subscription.dispose()
will print
[my probe] subscribed // 구독 시작
Subscribed // myInterval에서 구독 시작
[my probe] -> Event next(Box(0)) // 첫 번째 이벤트(Box로 감싸진 0)
This is simply 0 // 변환된 데이터 출력
[my probe] -> Event next(Box(1)) // 두 번째 이벤트(Box로 감싸진 1)
This is simply 1 // 변환된 데이터 출력
[my probe] -> Event next(Box(2)) // 세 번째 이벤트(Box로 감싸진 2)
This is simply 2
[my probe] -> Event next(Box(3)) // 네 번째 이벤트(Box로 감싸진 3)
This is simply 3
[my probe] -> Event next(Box(4)) // 다섯 번째 이벤트(Box로 감싸진 4)
This is simply 4
[my probe] dispose // 구독 취소
Disposed // myInterval 구독 취소
debug는 스트림에서 발생하는 모든 이벤트(next, error, completed, disposed)를 출력합니다.
스트림의 상태를 실시간으로 확인할 수 있어 디버깅에 유용합니다.
이런식으로 레이블과 함꼐 어떤 Observable에서 이벤트가 발생했는지 쉽게 파악할수 있다.
이벤트 흐름을 “탐침”하듯이 관찰하여, 특정 Observable이 예상대로 동작하는지 확인합니다.
You can also easily create your version of the debug
operator.
extension ObservableType {
public func myDebug(identifier: String) -> Observable<Self.E> {
return Observable.create { observer in
print("subscribed \(identifier)")
let subscription = self.subscribe { e in
print("event \(identifier) \(e)")
switch e {
case .next(let value):
observer.on(.next(value))
case .error(let error):
observer.on(.error(error))
case .completed:
observer.on(.completed)
}
}
return Disposables.create {
print("disposing \(identifier)")
subscription.dispose()
}
}
}
}
• debug 연산자는 Observable 스트림에서 발생하는 모든 이벤트를 출력하여 디버깅과 스트림 분석에 유용합니다.
• 복잡한 RxSwift 코드에서 이벤트 흐름을 추적하거나, 에러 발생 원인을 파악할 때 유용하게 활용할 수 있습니다.
• debug는 개발 중에만 사용하고, 배포 코드에서는 제거하여 성능에 영향을 주지 않도록 하는 것이 좋습니다.
Enabling Debug Mode
디버그 모드의 활성화
In order to Debug memory leak using RxSwift.Resources or Log all HTTP request automatically, you have to enable Debug Mode.
RxSwift의 RxSwift.Resource 를 사용해 메모리 누수 디버깅을 하거나 HTTP 요청을 자동으로 로깅하려면 디버그 모드를 활성화 해야합니다.
In order to enable debug mode, a TRACE_RESOURCE flag must be added to the RxSwift target build settings, under Other Swift Flags.
디버그 모드를 활성화 하려면 RxSwift 타겟의 빌드 설정에서 Other Swift Flags 에 TRACE_RESOURCE 플래그를 추가해야 합니다.
For futher discussion and instruction on how to set the TRACE_RESOURECES flags for Cocoapods & Carthage , see #378
Debugging memory leaks
In debug mode Rx tracks all allocated resources in a global variable Resource.total.
In case you want to have some resource leak detection logic, the simplest method is just priniting out RxSwift.
Resources.total periodically to output.
메모리누수 디버깅
디버깅 모드에서는 RxSwift가 할당된 모든 리소스를 전역변수 Resource.total에서 추적합니다.
리소스 누수 탐지 로직을 구현하려면, 주기적으로 RxSwift.Resource.total 값을 출력하는 방법이 제일 안전합니다.
/* 다음 코드를 앱 시작 시점에 추가:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) */
_ = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
.subscribe(onNext: { _ in
print("Resource count \(RxSwift.Resources.total)")
})
The most efficient way to test for memory leaks is:
- navigate to your screen and use it
- navigate back
- observe initial resource count
- navigate second time to your screen and use it
- navigate back
- observe final resource count
In case there is a difference count between initial and final resource counts, there might be a memory somewhere.
The reason why 2 navigations are suggested is because first navigation forces loading of lazy resources.
메모리 누수를 테스트하는 가장 효율적인 방법:
- 화면으로 이동하고 사용합니다.
- 이전 화면으로 돌아옵니다.
- 초기 리소스 개수를 관찰합니다.
- 화면으로 다시 이동하고 사용합니다.
- 다시 이전 화면으로 돌아옵니다.
- 최종 리소스 개수를 관찰합니다.
초기와 최종 리소스 개수가 다르다면, 메모리 누수(memory leak) 가 발생했을 가능성이 있습니다.
두 번의 화면 전환을 권장하는 이유는, 첫 번째 전환에서 지연 로딩(lazy loading) 리소스가 로드되기 때문입니다.
RxSwift는 메모리 관리가 중요함
Observable체인의 구독이나 리소스 해제가 제대로 이루어지지 않으면 메모리 누수가 발생할수 있음
그래서 Resource.total을 활용하여서 현재 RxSwift에서 활성상태인 리소스 개수를 확인하고 누수를 탐지할수 잇음
화면전환 테스트 외에도 매초마다 현재리소스 개수를 출력하는 방법도 있음
_ = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
.subscribe(onNext: { _ in
print("Resource count \(RxSwift.Resources.total)")
})
이런식으로 매초마다 현재 리소스 개수를 출력하여 확인하는 방법도 있음
KVO
(Key-Value Observing)
KVO is an Objective-C mechanism. That means that it wasn't built with type safety in mind.
This project tries to solve some of the problems.
There are two built in ways this library supports KVO.
KVO는 Objective-C 기반의 객체속성 변화를 감지하는 기능입니다. 하지만 타입안전성이 부족하고 메모리 관리가 복잡하다는 단점이 있습니다.
RxSwift는 KVO를 위한 두가지 메서드를 제공합니다.
- observe
- observeWeakly
extension Reactive where Base: NSObject {
public func observe<E>(
type: E.Type,
_ keyPath: String,
options: KeyValueObservingOptions,
retainSelf: Bool = true
) -> Observable<E?> {}
}
#if !DISABLE_SWIZZLING
extension Reactive where Base: NSObject {
public func observeWeakly<E>(
type: E.Type,
_ keyPath: String,
options: KeyValueObservingOptions
) -> Observable<E?> {}
}
#endif
Example how to observe frame of UIView.
Warning: UIKit isn't KVO compliant, but this will work.
UIView에서 frame을 관찰하는 예시
경고 UIKit은 KVO를 완벽하게 지원하지 않지만 , 이방법은 동작합니다.
view
.rx.observe(CGRect.self, "frame")
.subscribe(onNext: { frame in
...
})
또는
view
.rx.observeWeakly(CGRect.self, "frame")
.subscribe(onNext: { frame in
...
})
- UIKit 의 속성들은 KVO로 감시하는것이 일반적으로 권장되지 않습니다.
- 하지만 RxSwift로 observe, observeWeakly를 통해 UIView의 frame과 같은 속성도 안정적으로 감시할수 있습니다.
rx.observe
rx.observe is more performant because it's just a simple wrapper around the KVO mechanism, but it has more limited usage scenarios.
- it can observe strong reference paths (retainSelf: false)
- It must observe only strong properties to avoid crashes.
rx.observe는 단순히 KVO를 감싼것이때문에 성능이 더 뛰어나지만, 사용에 제약이 있습니다.
- 강한참조 속성만 감시할수 있습니다.
- retainSelf를 false로 설정하면, 자기자신을 참조하지 않음으로써 메모리 누수를 방지 합니다.
- 약한 참조나 복잡한 객체 관계에서는 충돌이 발생할수 있습니다.
observe는 성능이 뛰어난 대신 감시할수있는 대상이 제한적입니다.
강한 참조속성만 관찰이 가능하고, 약한참조 나 복잡한관계에서는 메모리 해제 문제가 발생할수있습니다
Eg)
self.rx.observe(CGRect.self, "view.frame", retainSelf: false)
rx.observeWeakly
rx.observeWeakly is somewhat slower because it handles obhect deallocation safely.
- It can observe weak references.
- It works even if ownership realations are unknown.
rx.observeWeakly 는 객체의 메모리 해제를 안전하게 처리하기때문에 약간 느립니다.
- 약한참조 속성도 감시할수 있습니다.
- 객체의 소유관계가 불명확 해도 안전하게 동작합니다.
성능이 약간 떨어지지만 , 약한참조나 불확실한 객체관계에서도 안전성을 보장합니다.
복잡한 객체 트리나 메모리 관리가 어려운 상황에서 더안전합니다.
Eg
someViewController.rx.observeWeakly(Bool.self, "isActive")
Observing Structs
KVO is an objective-C mechanism, so it relies heavily on NSValue.
RxCocoa has built-in support for observing CGRect, CGSize, CGPoint.
구조체 감시
KVO는 Objective -C 기반이기때문에 주로 NSValue를 사용합니다.
RxCocoa는 CGRect, CGSize, CGPoint와 같은 구조체의 감시를 지원합니다.
• KVO는 객체(Object) 감시에 최적화되어 있어 구조체(Struct) 감시가 어렵습니다.
• RxCocoa는 자주 사용하는 CGRect, CGSize, CGPoint와 같은 구조체의 감시를 내장 지원합니다.
• 다른 구조체를 감시하려면 NSValue로 변환하거나, KVORepresentable을 구현해야 합니다.
최종요약
• RxSwift는 기존 Objective-C의 KVO의 복잡성과 불안정성을 해결합니다.
• observe는 성능이 뛰어나지만 강한 참조만 감시 가능.
• observeWeakly는 약한 참조도 감시 가능하지만 성능이 약간 느립니다.
• UIKit 속성도 RxSwift를 통해 안전하게 감시할 수 있습니다.