Exploring Dynamic Method Injection in Swift

Giuseppe Travasoni
2 min readDec 6, 2023

Hello Swift enthusiasts! In today’s article, we’re diving into an intriguing aspect of Swift programming — dynamic method injection. This technique allows developers to add methods to classes or objects at runtime, offering a flexible approach to managing functionalities. We’ll explore this concept through a code snippet that injects a method from one class into another.

What is Method Injection?

Method Injection is a process where you dynamically add new methods to a class or an instance of a class at runtime. This is not typically a Swift-native approach, as Swift emphasizes type safety and compile-time checks. However, Swift’s interoperability with Objective-C runtime offers a backdoor to achieve this.

The Code Breakdown

Here’s a brief overview of the code snippet we’re examining:

Class Definitions

  • ClassA: Contains a hello() method that prints the class's name. hello() method must have @objc attribute because it will be user with Objective-C Runtime soon.
  • ClassB: An empty class without any methods.
class ClassA {
@objc
func hello() {
print("Hello, i'm " + String(describing: type(of: self)))
}
}

class ClassB { }

Method Injection Function

  • useMethod(sel:fromClass:inObject:): Injects the method from fromClass into inObject.
  • It employs class_getInstanceMethod and unsafeBitCast to dynamically add the method to the object.
func useMethod(sel: Selector, fromClass: AnyClass, inObject: AnyObject) {

guard let meth = class_getInstanceMethod(fromClass, sel) else {
return
}

typealias ClosureType = @convention(c) (AnyObject, Selector) -> Void
let imp = method_getImplementation(meth)
let callMethod: ClosureType = unsafeBitCast(imp, to: ClosureType.self)
callMethod(inObject, sel)
}

Implementation

  • An instance of ClassA is created and its hello() method is called.
  • An instance of ClassB is then created, and the hello method from ClassA is injected and executed on it using useMethod.
let a = ClassA()
a.hello()

let b = ClassB()
useMethod(sel: #selector(ClassA.hello), fromClass:ClassA.self, inObje

Deep Dive into the Function

The useMethod function is where the magic happens. Let's dissect it:

  • It first checks if the method exists in the source class using class_getInstanceMethod.
  • Once the method is obtained, it retrieves its implementation (IMP).
  • This implementation is then cast to a closure of type @convention(c) (AnyObject, Selector) -> Void using unsafeBitCast.
  • Finally, the method is invoked on the target object.

You can find full playground in this gist.

Why is This Important?

This method showcases an advanced level of Swift’s capabilities, blending Swift’s modern features with the underlying Objective-C runtime. It demonstrates how Swift can handle dynamic behavior, a feature often associated with more dynamic languages like Python or JavaScript.

Cautions

While powerful, method injection should be used judiciously. It bypasses Swift’s type safety and can lead to unpredictable behaviors or crashes if not handled properly. Always ensure that the method and the target object are compatible.

Conclusion

Dynamic method injection in Swift, while not a common practice, opens doors to a range of possibilities for developers willing to explore the depths of the language. It exemplifies Swift’s versatility and its seamless blend with Objective-C runtime, offering a unique approach to solving complex programming challenges.

--

--

Giuseppe Travasoni

Co-Founder at TrueScreen // Co-Founder at Beatcode // iOS Developer and Architect