Skip to content

Instantly share code, notes, and snippets.

@keith
Last active January 9, 2024 07:52
Show Gist options
  • Save keith/da9e0c1eabab309ba924c8901abb0c00 to your computer and use it in GitHub Desktop.
Save keith/da9e0c1eabab309ba924c8901abb0c00 to your computer and use it in GitHub Desktop.
A test of `@_dynamicReplacement` in Swift

Usage

  1. swiftc main.swift -emit-module-path main.swiftmodule -emit-executable -enable-private-imports -Xfrontend -enable-implicit-dynamic
  2. ./main -> prints From original bar()
  3. swiftc -emit-library inject.swift -o inject.dylib -I . -Xlinker -undefined -Xlinker suppress -Xlinker -flat_namespace -Xfrontend -disable-access-control
  4. DYLD_INSERT_LIBRARIES=inject.dylib ./main -> prints From replacement bar()

Notes

  • Passing -Xfrontend -enable-implicit-dynamic removes you from having to add dynamic to everything you want to be replacable
  • Passing -enable-private-imports to the main.swift build, and then -Xfrontend -disable-access-control to the dylib build stops you from having to make things public unnecessarily. Depending on the use case if the symbols are public already you could remove these flags
  • The dylib compilation depends on the main module. I was hoping I could use a string or something instead and specify main.Foo.bar explicitly instead of having to depend main directly for building
  • There must be a better way than passing -undefined suppress -flat_namespace to the linker for the dylib compilation. You could pass -U SYMBOL but I think that's a bit more annoying to track down and maintain

Useful reference: https://tech.guardsquare.com/posts/swift-native-method-swizzling

import main
extension Foo {
@_dynamicReplacement(for: bar)
func replacementBar() {
print("From replacement bar()")
}
}
struct Foo {
func bar() {
print("From original bar()")
}
}
Foo().bar()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment