RxSwift Resource Tracking

Prerequisites

Main project—DEBUG flag

Pods project—TRACE RESOURCES flag

post_install do |installer|
    installer.pods_project.targets.each do |target|
        if target.name == 'RxSwift'
            target.build_configurations.each do |config|
                if config.name == 'Debug' || config.name == 'StagingDebug' || config.name == 'ProductionDebug'
                    config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['$(inherited)', '-D', 'TRACE_RESOURCES']
                end
            end
        end
    end
end

Third-party stuff

Implementation

ViewController-based tracking (preferred way)

import UIKit
import RxSwift

class ViewController: UIViewController {

    #if DEBUG
        private let startResourceCount = Resources.total
    #endif

    // MARK: - Public -

    // MARK: - Overrides -

    // MARK: - Lifecycle -

    override func viewDidLoad() {
        super.viewDidLoad()
        #if DEBUG
            let logString = "⚠️ Number of start resources = \(Resources.total) ⚠️"
            log.info(logString)
        #endif
    }

    deinit {
        #if DEBUG

            let mainQueue = DispatchQueue.main
            let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(UIApplication.isInUITest ? 1000 : 300)

            mainQueue.asyncAfter (deadline: when) {
                let logString = "⚠️ Number of resources after dispose = \(Resources.total) ⚠️"
                log.info(logString)
            }

            /*
             !!! This cleanup logic is adapted for example app use case. !!!

             It is being used to detect memory leaks during pre-release tests.

             !!! In case you want to have some resource leak detection logic, the simplest
             method is to just print out `RxSwift.Resources.total` periodically to output. !!!


             /* add somewhere in
             func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
             */
             _ = Observable<Int>.interval(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 the initial resource count
             * navigate to your screen once more and use it
             * navigate back
             * observe final resource count

             In case there is a difference in resource count between the initial and final resource counts, there might be a memory
             leak somewhere.

             The reason why we suggest two navigations is because the first navigation forces loading of lazy resources.
             */

        #endif
    }
}

Periodic sampling

_ = Observable<Int>
        .interval(1, scheduler: MainScheduler.instance)
        .subscribe(onNext: { _ in
             print("Resource count \(RxSwift.Resources.total)")
        })

Testing

Troubleshooting (if you have memory leaks)