Skip to content

Trace Swift and Objective-C method invocations

License

Notifications You must be signed in to change notification settings

tomattoz/SwiftTrace

 
 

Repository files navigation

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!

About

Trace Swift and Objective-C method invocations

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Assembly 90.2%
  • Swift 7.7%
  • Objective-C++ 1.5%
  • Other 0.6%