{"id":1345,"url":"https://github.com/johnno1962/SwiftTrace","last_synced_at":"2025-08-02T04:30:57.832Z","repository":{"id":41906132,"uuid":"60823979","full_name":"johnno1962/SwiftTrace","owner":"johnno1962","description":"Trace Swift and Objective-C method invocations","archived":false,"fork":false,"pushed_at":"2024-06-25T03:22:12.000Z","size":3803,"stargazers_count":735,"open_issues_count":1,"forks_count":53,"subscribers_count":19,"default_branch":"main","last_synced_at":"2025-07-24T07:48:51.402Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnno1962.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"johnno1962"}},"created_at":"2016-06-10T04:27:09.000Z","updated_at":"2025-07-18T01:04:36.000Z","dependencies_parsed_at":"2024-01-07T22:23:21.105Z","dependency_job_id":"133ff7af-7ec0-431f-94aa-1b639b02dff9","html_url":"https://github.com/johnno1962/SwiftTrace","commit_stats":{"total_commits":209,"total_committers":3,"mean_commits":69.66666666666667,"dds":"0.014354066985645897","last_synced_commit":"0443b560ad6d1936ad7cbf0733cfa2ce74ae6e2e"},"previous_names":[],"tags_count":120,"template":false,"template_full_name":null,"purl":"pkg:github/johnno1962/SwiftTrace","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FSwiftTrace","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FSwiftTrace/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FSwiftTrace/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FSwiftTrace/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnno1962","download_url":"https://codeload.github.com/johnno1962/SwiftTrace/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FSwiftTrace/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268334609,"owners_count":24233793,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:15:44.308Z","updated_at":"2025-08-02T04:30:57.331Z","avatar_url":"https://github.com/johnno1962.png","language":"Swift","funding_links":["https://github.com/sponsors/johnno1962"],"categories":["Logging","Swift"],"sub_categories":["Other Hardware","Other free courses"],"readme":"# SwiftTrace\n\nTrace Swift and Objective-C method invocations of non-final classes in an app bundle or framework.\nThink [Xtrace](https://github.com/johnno1962/Xtrace) but for Swift and Objective-C. You can also \nadd \"aspects\" to member functions of non-final Swift classes to have a closure called before or after\na function implementation executes which in turn can modify incoming arguments or the return value!\nApart from the logging functionality, with binary distribution of Swift frameworks on the horizon perhaps\nthis will be of use in the same way \"Swizzling\" was in days of yore.\n\n![SwiftTrace Example](SwiftTrace.gif)\n\nNote: none of these features will work on a class or method that is final or internal in \na module compiled with whole module optimisation as the dispatch of the method\nwill be \"direct\" i.e. linked to a symbol at the call site rather than going through the\nclass' vtable. As such it is possible to trace calls to methods of a struct but only\nif they are referenced through a protocol as they use a `witness table` which\ncan be patched.\n\nSwiftTrace can be used with the Swift Package Manager or as a CocoaPod by\nadding the following to your project's Podfile:\n\n```Swift\n    pod 'SwiftTrace'\n ```\nOnce the project has rebuilt, import SwiftTrace into the application's AppDelegate and add something like the following to the beginning of\nit's didFinishLaunchingWithOptions method:\n\n```Swift\n    SwiftTrace.traceBundle(containing: type(of: self))\n ```\nThis traces all classes defined in the main application bundle.\nTo trace, for example, all classes in the RxSwift Pod add the following\n\n```Swift\n    SwiftTrace.traceBundle(containing: RxSwift.DisposeBase.self)\n ```\nThis gives output in the Xcode debug console such as that above.\n\nTo trace a system framework such as UIKit you can trace classes using a pattern:\n\n```Swift\n    SwiftTrace.traceClasses(matchingPattern:\"^UI\")\n ```\nIndividual classes can be traced using the underlying api:\n\n```Swift\n    SwiftTrace.trace(aClass: MyClass.self)\n```\nOr to trace all methods of instances of a particular class including those of their superclasses\nuse the following:\n\n```Swift\n    SwiftTrace.traceInstances(ofClass: aClass)\n```\nOr to trace only a particular instance use the following:\n\n```Swift\n    SwiftTrace.trace(anInstance: anObject)\n```\nIf you have specified `\"-Xlinker -interposable\"` in your project's `\"Other Linker Flags\"`\nit's possible to trace all methods in the application's main bundle at once which can be\nuseful for profiling `SwiftUI` using the following call:\n\n```Swift\n    SwiftTrace.traceMainBundleMethods()\n```\nIt is possible to trace methods of a structs or other types if they are messaged through\nprotools as this would then be indirect via what is called a `witness table`. Tracing\nprotocols is available at the bundle level where the bundle being traced is specified\nusing a class instance. They can be further filtered by an optional regular expression. \nFor example, the following:\n```Swift\nSwiftTrace.traceProtocolsInBundle(containing: AClassInTheBundle.self, matchingPattern: \"regexp\")\n```\nFor example, to trace internal calls made in the `SwiftUI` framework you can use the following:\n```Swift\nfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n    SwiftTrace.traceProtocolsInBundle(containing: UIHostingController\u003cHomeView\u003e.self)\n    return true\n}\n```\n\nWhich traces are applied can be filtered using method name inclusion and exclusion regexps. \n```swift\n    SwiftTrace.methodInclusionPattern = \"TestClass\"\n    SwiftTrace.methodExclusionPattern = \"init|\"+SwiftTrace.defaultMethodExclusions\n```\nThese methods must be called before you start the trace as they are applied during the \"Swizzle\" phase.\nThere is a default set of exclusions setup as a result of testing by tracing UIKit.\n                      \n    open class var defaultMethodExclusions: String {\n        return \"\"\"\n            \\\\.getter| (?:retain|_tryRetain|release|_isDeallocating|.cxx_destruct|dealloc|description| debugDescription)]|initWithCoder|\\\n            ^\\\\+\\\\[(?:Reader_Base64|UI(?:NibStringIDTable|NibDecoder|CollectionViewData|WebTouchEventsGestureRecognizer)) |\\\n            ^.\\\\[(?:UIView|RemoteCapture) |UIDeviceWhiteColor initWithWhite:alpha:|UIButton _defaultBackgroundImageForType:andState:|\\\n            UIImage _initWithCompositedSymbolImageLayers:name:alignUsingBaselines:|\\\n            _UIWindowSceneDeviceOrientationSettingsDiffAction _updateDeviceOrientationWithSettingObserverContext:windowScene:transitionContext:|\\\n            UIColorEffect colorEffectSaturate:|UIWindow _windowWithContextId:|RxSwift.ScheduledDisposable.dispose| ns(?:li|is)_\n            \"\"\"\n    }\n\nIf you want to further process output you can define your own custom tracing sub class:\n```swift\n    class MyTracer: SwiftTrace.Decorated {\n\n        override func onEntry(stack: inout SwiftTrace.EntryStack) {\n            print( \"\u003e\u003e \"+stack )\n        }\n    }\n    \n    SwiftTrace.swizzleFactory = MyTracer.self\n ```   \n As the amount of of data logged can quickly get out of hand you can control what is\n logged by combing traces with the optional `subLevels` parameter to the above functions.\n For example, the following puts a trace on all of UIKit but will only log calls to methods\n of the target instance and up to three levels of calls those method make:\n ```Swift\n     SwiftTrace.traceBundle(containing: UIView.self)\n     SwiftTrace.trace(anInstance: anObject, subLevels: 3)\n ```\n Or, the following will log methods of the application and calls to RxSwift they make:\n ```Swift\n     SwiftTrace.traceBundle(containing: RxSwift.DisposeBase.self)\n     SwiftTrace.traceMainBundle(subLevels: 3)\n ```\n If this seems arbitrary the rules are reasonably simple. When you add a trace with a\n non-zero subLevels parameter all previous traces are inhibited unless they are being\n made up to subLevels inside a method in the most recent trace or if they where filtered\n anyway by a class or instance (traceInstances(ofClass:) and trace(anInstance:)).\n\nIf you would like to extend SwiftTrace to be able to log one of your app's types\nthere are two steps. First, you may need to extend the type to conform to\nSwiftTraceFloatArg if it contains only float only float types for example SwiftUI.EdgeInsets.\n```Swift\nextension SwiftUI.EdgeInsets: SwiftTraceFloatArg {}\n```\nThen, add a handler for the type using the following api:\n```Swift\n    SwiftTrace.addFormattedType(SwiftUI.EdgeInsets.self, prefix: \"SwiftUI\")\n```\n Many of these API's are also available as a extension of NSObject which is useful\n when SwiftTrace is made available by dynamically loading bundle as in\n (InjectionIII)[https://github.com/johnno1962/InjectionIII].\n ```Swift\n     SwiftTrace.traceBundle(containing: UIView.class)\n     // becomes\n     UIView.traceBundle()\n     \n     SwiftTrace.trace(inInstance: anObject)\n     // becomes\n     anObject.swiftTraceInstance()\n ```\n This is useful when SwiftTrace is made available by dynamically loading a bundle\n such as when using (InjectionIII)[https://github.com/johnno1962/InjectionIII]. Rather\n than having to include a CocoaPod, all you need to do is add SwiftTrace.h in the\n InjectionIII application's bundle to your bridging header and dynamically load the bundle.\n ```Swift\n    Bundle(path: \"/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle\")?.load()\n ```\n #### Benchmarking\n \n To benchmark an app or framework, trace it's methods then you can use one of the following:\n ```\n    SwiftTrace.sortedElapsedTimes(onlyFirst: 10))\n    SwiftTrace.sortedInvocationCounts(onlyFirst: 10))\n ```\n#### Object lifetime tracking\n\nYou can track the allocations an deallocations of Swift and\nObjective-C classes using the [SwiftTrace.LifetimeTracker](https://github.com/johnno1962/SwiftTrace/blob/main/SwiftTrace/SwiftLifetime.swift) class:\n\n```Swift\nSwiftTrace.swizzleFactory = SwiftTrace.LifetimeTracker.self\nSwiftTrace.traceMainBundleMethods() == 0 {\n    print(\"⚠️ Tracing Swift methods can only work if you have -Xlinker -interposable to your project's \\\"Other Linker Flags\\\"\")\n}\nSwiftTrace.traceMainBundle()\n```\nEach time an object is allocated you will see a `.__allocating_init` message\nfollowed by the result and the resulting count of live objects allocated \nsince tracing was started. Each time an object is deallocated you will\nsee a `cxx_destruct` message followed by the number of objects\noustanding for that class.\n\nIf you would like to track the lifecycle of Swift structs, create a marker\nclass and add a property to the struct initialised to an instance of it.\n\n```Swift\nclass Marker\u003cWhat\u003e {}\n\nstruct MyView: SwiftUI.View {\n    var marker = Marker\u003cMyView\u003e()\n}\n```\nThis idea is based on the [LifetimeTracker](https://github.com/krzysztofzablocki/LifetimeTracker)\nproject by [Krzysztof Zabłocki](https://github.com/krzysztofzablocki).\n#### Aspects\n\nYou can add an aspect to a particular method using the method's de-mangled name:\n```swift\n    print(SwiftTrace.addAspect(aClass: TestClass.self,\n                      methodName: \"SwiftTwaceApp.TestClass.x() -\u003e ()\",\n                      onEntry: { (_, _) in print(\"ONE\") },\n                      onExit: { (_, _) in print(\"TWO\") }))\n ```   \nThis will print \"ONE\" when method \"x\" of TextClass is called and \"TWO when it has exited. The\ntwo arguments are the Swizzle which is an object representing the \"Swizzle\" and the entry or \nexit stack. The full signature for the entry closure is:\n```swift\n       onEntry: { (swizzle: SwiftTrace.Swizzle, stack: inout SwiftTrace.EntryStack) in\n ```   \nIf you understand how [registers are allocated](https://github.com/apple/swift/blob/master/docs/ABI/RegisterUsage.md) to arguments it is possible to poke into the\nstack to modify the incoming arguments and, for the exit aspect closure you can replace\nthe return value and on a good day log (and prevent) an error being thrown.\n\nReplacing an input argument in the closure is relatively simple:\n```swift\n    stack.intArg1 = 99\n    stack.floatArg3 = 77.3\n ```\nOther types of argument a little more involved. They must be cast and String\ntakes up two integer registers.\n```swift\n    swizzle.rebind(\u0026stack.intArg2).pointee = \"Grief\"\n    swizzle.rebind(\u0026stack.intArg4).pointee = TestClass()\n ```\nIn an exit aspect closure, setting the return type is easier as it is generic:\n```swift\n    stack.setReturn(value: \"Phew\")\n ```\nWhen a function throws you can access NSError objects.\n```swift\n    print(swizzle.rebind(\u0026stack.thrownError, to: NSError.self).pointee)\n ```\nIt is possible to set `stack.thrownError` to zero to cancel the throw but you will need to set\nthe return value.\n\nIf this seems complicated there is a property `swizzle.arguments` which can be used\n`onEntry` which contains the arguments as an `Array` containing elements of type `Any`\nwhich can be cast to the expected type. Element 0 is `self`.\n\n#### Invocation interface\n\nNow we have a trampoline infrastructure, it is possible to implement an invocation api for Swift:\n```swift\n    print(\"Result: \"+SwiftTrace.invoke(target: b,\n        methodName: \"SwiftTwaceApp.TestClass.zzz(_: Swift.Int, f: Swift.Double, g: Swift.Float, h: Swift.String, f1: Swift.Double, g1: Swift.Float, h1: Swift.Double, f2: Swift.Double, g2: Swift.Float, h2: Swift.Double, e: Swift.Int, ff: Swift.Int, o: SwiftTwaceApp.TestClass) throws -\u003e Swift.String\",\n        args: 777, 101.0, Float(102.0), \"2-2\", 103.0, Float(104.0), 105.0, 106.0, Float(107.0), 108.0, 888, 999, TestClass()))\n ```\nIn order to determine the mangled name of a method you can get the full list for a class \nusing this function:\n```swift\n    print(SwiftTrace.methodNames(ofClass: TestClass.self))\n ```\nThere are limitations to this abbreviated interface in that it only supports Double, Float,\nString, Int, Object, CGRect, CGSize and CGPoint arguments. For other struct types that\ndo not contain floating point values you can conform them to protocol `SwiftTraceArg`\nto be able to pass them on the argument list or `SwiftTraceFloatArg` if they contain\nonly floats. These values and return values must fit into 32 bytes and not contain floats.\n\n#### How it works\n                      \nA Swift `AnyClass` instance has a layout similar to an Objective-C class with some\nadditional data documented in the `ClassMetadataSwift` in SwiftMeta.swift. After this data\nthere is the vtable of pointers to the class and instance member functions of the class up to\nthe size of the class instance. SwiftTrace replaces these function pointers with a pointer\nto a unique assembly language \"trampoline\" entry point which has destination function and\ndata pointers associated with it. Registers are saved and this function is called passing\nthe data pointer to log the method name. The method name is determined by de-mangling the\nsymbol name associated the function address of the implementing method. The registers are\nthen restored and control is passed to the original function implementing the method. \n \nPlease file an issue if you encounter a project that doesn't work while tracing. It should\nbe far more reliable as it uses assembly language trampolines rather than Swizzling like\nXtrace did. Otherwise, the author can be contacted on Twitter [@Injection4Xcode](https://twitter.com/@Injection4Xcode). \n\nThanks to Oliver Letterer for the [imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) project adapted to set up the\ntrampolines, included under an MIT license.\n\nThe repo includes a very slightly modified version of the very handy\n[https://github.com/facebook/fishhook](https://github.com/facebook/fishhook).\nSee the source and header files for their licensing details.\n\nThanks also  to [@twostraws](https://twitter.com/twostraws)'\n[Unwrap](https://github.com/twostraws/Unwrap) and [@artsy](https://twitter.com/ArtsyOpenSource)'s\n[eidolon](https://github.com/artsy/eidolon) used extensively during testing.\n\nEnjoy!\n\n$Date: 2022/01/22 $\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnno1962%2FSwiftTrace","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnno1962%2FSwiftTrace","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnno1962%2FSwiftTrace/lists"}