Exploring Dynamic Method Injection in Swift

neobeppe

Giuseppe Travasoni

Posted on December 6, 2023

Exploring Dynamic Method Injection in Swift

Introduction

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 { }
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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, inObject: b)
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
neobeppe
Giuseppe Travasoni

Posted on December 6, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related