{"id":13995276,"url":"https://github.com/steipete/InterposeKit","last_synced_at":"2025-07-22T21:32:31.441Z","repository":{"id":41365183,"uuid":"262974637","full_name":"steipete/InterposeKit","owner":"steipete","description":"A modern library to swizzle elegantly in Swift.","archived":false,"fork":false,"pushed_at":"2022-12-16T08:09:18.000Z","size":5724,"stargazers_count":977,"open_issues_count":13,"forks_count":48,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-04-24T16:18:55.199Z","etag":null,"topics":["aspects","hook","interpose","swift","swizzling"],"latest_commit_sha":null,"homepage":"https://interposekit.com/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/steipete.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-11T07:47:40.000Z","updated_at":"2024-04-23T08:51:21.000Z","dependencies_parsed_at":"2022-09-03T21:01:30.023Z","dependency_job_id":null,"html_url":"https://github.com/steipete/InterposeKit","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/steipete%2FInterposeKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/steipete%2FInterposeKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/steipete%2FInterposeKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/steipete%2FInterposeKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/steipete","download_url":"https://codeload.github.com/steipete/InterposeKit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":214675528,"owners_count":15768169,"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","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":["aspects","hook","interpose","swift","swizzling"],"created_at":"2024-08-09T14:03:20.006Z","updated_at":"2024-08-09T14:16:06.517Z","avatar_url":"https://github.com/steipete.png","language":"Swift","funding_links":[],"categories":["Swift"],"sub_categories":[],"readme":"\u003cimg src=\"https://raw.githubusercontent.com/steipete/InterposeKit/master/logo.png\" width=\"60%\" alt=\"InterposeKit\"/\u003e\n\n[![SwiftPM](https://github.com/steipete/InterposeKit/workflows/SwiftPM/badge.svg)](https://github.com/steipete/InterposeKit/actions?query=workflow%3ASwiftPM)\n[![xcodebuild](https://github.com/steipete/InterposeKit/workflows/xcodebuild/badge.svg)](https://github.com/steipete/InterposeKit/actions?query=workflow%3Axcodebuild)\n[![pod lib lint](https://github.com/steipete/InterposeKit/workflows/pod%20lib%20lint/badge.svg)](https://github.com/steipete/InterposeKit/actions?query=workflow%3A%22pod+lib+lint%22)\n![Xcode 11.4+](https://img.shields.io/badge/Xcode-11.4%2B-blue.svg)\n![Swift 5.2+](https://img.shields.io/badge/Swift-5.2%2B-orange.svg)\n\u003c!--\n[![codecov](https://codecov.io/gh/steipete/InterposeKit/branch/master/graph/badge.svg)](https://codecov.io/gh/steipete/InterposeKit) --\u003e\n\nInterposeKit is a modern library to swizzle elegantly in Swift, supporting hooks on classes and individual objects. It is [well-documented](http://interposekit.com/), [tested](https://github.com/steipete/InterposeKit/actions?query=workflow%3ASwiftPM), written in \"pure\" Swift 5.2 and works on `@objc dynamic` Swift functions or Objective-C instance methods. The Inspiration for InterposeKit was [a race condition in Mac Catalyst](https://steipete.com/posts/mac-catalyst-crash-hunt/), which required tricky swizzling to fix, I also wrote up  [implementation thoughts on my blog](https://steipete.com/posts/interposekit/).\n\nInstead of [adding new methods and exchanging implementations](https://nshipster.com/method-swizzling/) based on [`method_exchangeImplementations`](https://developer.apple.com/documentation/objectivec/1418769-method_exchangeimplementations), this library replaces the implementation directly using [`class_replaceMethod`](https://developer.apple.com/documentation/objectivec/1418677-class_replacemethod). This avoids some of [the usual problems with swizzling](https://pspdfkit.com/blog/2019/swizzling-in-swift/).\n\nYou can call the original implementation and add code before, instead or after a method call.  \nThis is similar to the [Aspects library](https://github.com/steipete/Aspects), but doesn't yet do dynamic subclassing.\n\nCompare: [Swizzling a property without helper and with InterposeKit](https://gist.github.com/steipete/f955aaa0742021af15add0133d8482b9) \n\n## Usage\n\nLet's say you want to amend `sayHi` from `TestClass`:\n\n```swift\nclass TestClass: NSObject {\n    // Functions need to be marked as `@objc dynamic` or written in Objective-C.\n    @objc dynamic func sayHi() -\u003e String {\n        print(\"Calling sayHi\")\n        return \"Hi there 👋\"\n    }\n}\n\nlet interposer = try Interpose(TestClass.self) {\n    try $0.prepareHook(\n        #selector(TestClass.sayHi),\n        methodSignature: (@convention(c) (AnyObject, Selector) -\u003e String).self,\n        hookSignature: (@convention(block) (AnyObject) -\u003e String).self) {\n            store in { `self` in\n                print(\"Before Interposing \\(`self`)\")\n                let string = store.original(`self`, store.selector) // free to skip\n                print(\"After Interposing \\(`self`)\")\n                return string + \"and Interpose\"\n            }\n    }\n}\n\n// Don't need the hook anymore? Undo is built-in!\ninterposer.revert()\n```\n\nWant to hook just a single instance? No problem!\n\n```swift\nlet hook = try testObj.hook(\n    #selector(TestClass.sayHi),\n    methodSignature: (@convention(c) (AnyObject, Selector) -\u003e String).self,\n    hookSignature: (@convention(block) (AnyObject) -\u003e String).self) { store in { `self` in\n        return store.original(`self`, store.selector) + \"just this instance\"\n        }\n}\n```\n\nHere's what we get when calling `print(TestClass().sayHi())`\n```\n[Interposer] Swizzled -[TestClass.sayHi] IMP: 0x000000010d9f4430 -\u003e 0x000000010db36020\nBefore Interposing \u003cInterposeTests.TestClass: 0x7fa0b160c1e0\u003e\nCalling sayHi\nAfter Interposing \u003cInterposeTests.TestClass: 0x7fa0b160c1e0\u003e\nHi there 👋 and Interpose\n```\n\n## Key Features\n\n- Interpose directly modifies the implementation of a `Method`, which is [safer than selector-based swizzling]((https://pspdfkit.com/blog/2019/swizzling-in-swift/)).\n- Interpose works on classes and individual objects.\n- Hooks can easily be undone via calling `revert()`. This also checks and errors if someone else changed stuff in between.\n- Mostly Swift, no `NSInvocation`, which requires boxing and can be slow.\n- No Type checking. If you have a typo or forget a `convention` part, this will crash at runtime.\n- Yes, you have to type the resulting type twice This is a tradeoff, else we need `NSInvocation`.\n- Delayed Interposing helps when a class is loaded at runtime. This is useful for [Mac Catalyst](https://steipete.com/posts/mac-catalyst-crash-hunt/).\n\n## Object Hooking\n\nInterposeKit can hook classes and object. Class hooking is similar to swizzling, but object-based hooking offers a variety of new ways to set hooks. This is achieved via creating a dynamic subclass at runtime. \n\nCaveat: Hooking will fail with an error if the object uses KVO. The KVO machinery is fragile and it's to easy to cause a crash. Using KVO after a hook was created is supported and will not cause issues.\n\n## Various ways to define the signature\n\nNext to using  `methodSignature` and `hookSignature`, following variants to define the signature are also possible:\n\n### methodSignature + casted block\n```\nlet interposer = try Interpose(testObj) {\n    try $0.hook(\n        #selector(TestClass.sayHi),\n        methodSignature: (@convention(c) (AnyObject, Selector) -\u003e String).self) { store in { `self` in\n            let string = store.original(`self`, store.selector)\n            return string + testString\n            } as @convention(block) (AnyObject) -\u003e String }\n}\n```\n\n### Define type via store object\n```\n// Functions need to be `@objc dynamic` to be hookable.\nlet interposer = try Interpose(testObj) {\n    try $0.hook(#selector(TestClass.returnInt)) { (store: TypedHook\u003c@convention(c) (AnyObject, Selector) -\u003e Int, @convention(block) (AnyObject) -\u003e Int\u003e) in {\n\n        // You're free to skip calling the original implementation.\n        let int = store.original($0, store.selector)\n        return int + returnIntOverrideOffset\n        }\n    }\n}\n```\n\n## Delayed Hooking\n\nSometimes it can be necessary to hook a class deep in a system framework, which is loaded at a later time. Interpose has a solution for this and uses a hook in the dynamic linker to be notified whenever new classes are loaded.\n\n```swift\ntry Interpose.whenAvailable([\"RTIInput\", \"SystemSession\"]) {\n    let lock = DispatchQueue(label: \"com.steipete.document-state-hack\")\n    try $0.hook(\"documentState\", { store in { `self` in\n        lock.sync {\n            store((@convention(c) (AnyObject, Selector) -\u003e AnyObject).self)(`self`, store.selector)\n        }} as @convention(block) (AnyObject) -\u003e AnyObject})\n\n    try $0.hook(\"setDocumentState:\", { store in { `self`, newValue in\n        lock.sync {\n            store((@convention(c) (AnyObject, Selector, AnyObject) -\u003e Void).self)(`self`, store.selector, newValue)\n        }} as @convention(block) (AnyObject, AnyObject) -\u003e Void})\n}\n```\n\n\n## FAQ\n\n### Why didn't you call it Interpose? \"Kit\" feels so old-school.\nNaming it Interpose was the plan, but then [SR-898](https://bugs.swift.org/browse/SR-898) came. While having a class with the same name as the module works [in most cases](https://forums.swift.org/t/frameworkname-is-not-a-member-type-of-frameworkname-errors-inside-swiftinterface/28962), [this breaks](https://twitter.com/BalestraPatrick/status/1260928023357878273) when you enable build-for-distribution. There's some [discussion](https://forums.swift.org/t/pitch-fully-qualified-name-syntax/28482/81) to get that fixed, but this will be more towards end of 2020, if even.\n\n### I want to hook into Swift! You made another ObjC swizzle thingy, why?\nUIKit and AppKit won't go away, and the bugs won't go away either. I see this as a rarely-needed instrument to fix system-level issues. There are ways to do some of that in Swift, but that's a separate (and much more difficult!) project. (See [Dynamic function replacement #20333](https://github.com/apple/swift/pull/20333) aka `@_dynamicReplacement` for details.)\n\n### Can I ship this?\nYes, absolutely. The goal for this one project is a simple library that doesn't try to be too smart. I did this in [Aspects](https://github.com/steipete/Aspects) and while I loved this to no end, it's problematic and can cause side-effects with other code that tries to be clever. InterposeKit is boring, so you don't have to worry about conditions like \"We added New Relic to our app and now [your thing crashes](https://github.com/steipete/Aspects/issues/21)\".\n\n### It does not do X!\nPull Requests welcome! You might wanna open a draft before to lay out what you plan, I want to keep the feature-set minimal so it stays simple and no-magic.\n\n## Installation\n\nBuilding InterposeKit requires Xcode 11.4+ or a Swift 5.2+ toolchain with the Swift Package Manager.\n\n### Swift Package Manager\n\nAdd `.package(url: \"https://github.com/steipete/InterposeKit.git\", from: \"0.0.1\")` to your\n`Package.swift` file's `dependencies`.\n\n### CocoaPods\n\n[InterposeKit is on CocoaPods](https://cocoapods.org/pods/InterposeKit). Add `pod 'InterposeKit'` to your `Podfile`.\n\n### Carthage\n\nAdd `github \"steipete/InterposeKit\"` to your `Cartfile`.\n\n## Improvement Ideas\n\n- Write proposal to allow to [convert the calling convention of existing types](https://twitter.com/steipete/status/1266799174563041282?s=21).\n- Use the C block struct to perform type checking between Method type and C type (I do that in  [Aspects library](https://github.com/steipete/Aspects)), it's still a runtime crash but could be at hook time, not when we call it.\n- Add a way to get all current hooks from an object/class.\n- Add a way to revert hooks without super helper.\n- Add a way to apply multiple hooks to classes\n- Enable hooking of class methods.\n- Add [dyld_dynamic_interpose](https://twitter.com/steipete/status/1258482647933870080) to hook pure C functions\n- Combine Promise-API for `Interpose.whenAvailable` for better error bubbling.\n- Experiment with [Swift function hooking](https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift)? ⚡️\n- Test against Swift Nightly as Cron Job\n- Switch to Trampolines to manage cases where other code overrides super, so we end up with a super call that's [not on top of the class hierarchy](https://github.com/steipete/InterposeKit/pull/15#discussion_r439871752).\n- I'm sure there's more - Pull Requests or [comments](https://twitter.com/steipete) very welcome!\n\nMake this happen:\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n![CocoaPods](https://img.shields.io/cocoapods/v/SwiftyJSON.svg)\n\n## Thanks\n\nSpecial thanks to [JP Simard](https://github.com/jpsim/Yams) who did such a great job in setting up [Yams](https://github.com/jpsim/Yams) with GitHub Actions - this was extremely helpful to build CI here fast.\n\n## License\n\nInterposeKit is MIT Licensed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsteipete%2FInterposeKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsteipete%2FInterposeKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsteipete%2FInterposeKit/lists"}