Trace Swift and Objective-C method invocations
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
SwiftTrace
SwiftTraceApp.xcodeproj
SwiftTraceApp
SwiftTraceOSX
SwiftTraceTests
SwiftTraceX
SwiftTraceXTests
.gitignore
LICENSE
README.md
SwiftTrace.podspec

README.md

SwiftTrace

Trace Swift and Objective-C method invocations of non-final classes in an app bundle or framework. Think Xtrace but for Swift and Objective-C.

SwiftTrace is most easily used as a CocoaPod and can be added to your project by temporarily adding the following line to it's Podfile:

pod 'SwiftTrace'

This project has been updated to Swift 3 from the Xocde 8 beta. If you want to use the Swift2 branch:

pod 'SwiftTrace', '2.1'

Once the project has rebuilt import SwiftTrace into the application's AppDelegate and add something like the following to the beginning of it's didFinishLaunchingWithOptions method:

SwiftTrace.traceBundleContaining( aClass: self.dynamicType )

This traces all classes defined in the main application bundle. To trace, for example, all classes in the RxSwift framework add the following

SwiftTrace.traceBundleContaining( aClass: RxSwift.DisposeBase.self )

This gives output in the Xcode debug console something like:

RxSwift.SingleAssignmentDisposable.dispose () -> ()
RxSwift.SingleAssignmentDisposable.disposable.setter : RxSwift.Disposable11
RxSwift.CompositeDisposable.addDisposable (RxSwift.Disposable11) -> Swift.Optional<RxSwift.BagKey>
RxSwift.CurrentThreadScheduler.schedule <A> (A, action : (A) -> RxSwift.Disposable11) -> RxSwift.Disposable11
-[RxSwift.CurrentThreadSchedulerKey copyWithZone:] -> @24@0:8^v16
RxSwift.CompositeDisposable.addDisposable (RxSwift.Disposable11) -> Swift.Optional<RxSwift.BagKey>
RxSwift.CompositeDisposable.addDisposable (RxSwift.Disposable11) -> Swift.Optional<RxSwift.BagKey>
RxSwift.SerialDisposable.disposable.setter : RxSwift.Disposable11
RxSwift.SingleAssignmentDisposable.dispose () -> ()
RxSwift.SingleAssignmentDisposable.disposable.setter : RxSwift.Disposable11
RxSwift.SingleAssignmentDisposable.dispose () -> ()
RxSwift.CompositeDisposable.removeDisposable (RxSwift.BagKey) -> ()
RxSwift.SingleAssignmentDisposable.dispose () -> ()

The line beginning "-[RxSwift" is where the old Objective-C dynamic dispatch is being used.

To trace a system framework such as UIKit you can trace classes using a pattern:

SwiftTrace.traceClassesMatching( pattern:"^UI" )

Individual classes can be traced using the underlying:

SwiftTrace.trace( aClass: MyClass.self )

Output can be filtered using inclusion and exclusion regexps.

SwiftTrace.include( pattern: "TestClass" )
SwiftTrace.exclude( pattern: "\\.getter" )

These methods must be called before you start the trace as they are applied during the "Swizzle". There is a default set of exclusions setup as a result of testing, tracing UIKit.

public let swiftTraceDefaultExclusions = "\\.getter|retain]|_tryRetain]|_isDeallocating]|^\\+\\[(Reader_Base64|UI(NibStringIDTable|NibDecoder|CollectionViewData|WebTouchEventsGestureRecognizer)) |^.\\[UIView |UIButton _defaultBackgroundImageForType:andState:|RxSwift.ScheduledDisposable.dispose"

If you want to further process output you can define a custom tracing class:

class MyTracer: SwiftTraceInfo {

    override func trace() -> IMP {
        print( ">> "+symbol )
        return original /// must return implmentation to call
    }
    
}

SwiftTrace.tracerClass = MyTracer.self

How it works

A Swift AnyClass instance has a layout similar to an Objective-C class with some additional data documented in the ClassMetadataSwift in SwiftTrace.swift. After this data there is a vtable of pointers to the class and instance member functions of the class up to the size of the class instance. SwiftTrace replaces these function pointers with a pointer to a unique assembly language "trampoline" entry point which has destination function and data pointers associated with it. Registers are saved and this function is called passing the data pointer to log the method name. The method name is determined by de-mangling the symbol name associated the function address of the implementing method. The registers are then restored and control is passed to the original function implementing the method.

Please file an issue if you encounter a project that doesn't work while tracing. It should be 100% reliable as it uses assembly language trampolines rather than Swizzling like Xtrace. Otherwise, the author can be contacted on Twitter @Injection4Xcode. Thanks to Oliver Letterer for imp_implementationForwardingToSelector used to set up the trampolines.

Enjoy!