iOS Interview - Swift Method Swizzling

April 07, 20246 min read#swift, #ios, #interview

What’s Method Swizzling?

Method swizzling is a technique in iOS programming that allows you to modify or replace the implementation of an existing method at runtime. It involves exchanging the implementations of two methods, typically to add or modify behavior without subclassing or modifying the original class.

What’s it for?

Common Use Cases:

  • Extending or modifying the behavior of existing methods in frameworks or third-party libraries without subclassing.
  • Debugging or logging purposes, such as tracking method calls or measuring performance.
  • Implementing cross-cutting concerns, such as authentication or caching, across multiple methods or classes.

Firebase is the most famouse SDK that uses method swizzling by default to simplify its setup. View Tracking is enabled automatically because FirebaseAnalytics will replace lifecycle methods of UIViewController with its own implementations for sending analytics events.

Another great example is Pulse, which swizzles the URLSession’s initialiser to capture network activities running through URLSession automatically.

How to swizzle a method?

Traditional method

Traditionally we often use Objective-C based methods such as class_getInstanceMethod, class_getClassMethod and method_exchangeImplementations to exchange the original implementation with its replacement.

Consider the following code in ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let someClass = SomeClass()
        someClass.originalMethod()
    }
}

class SomeClass {
    @objc dynamic func originalMethod() {
        print("I am the original method")
    }
}

Running the above code, we’ll see the originalMethod output correctly

I am the original method

Now we will add some code to exchange the originalMethod implementation with swizzledOriginalMethod implementation

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let selector1 = #selector(SomeClass.originalMethod)
        let selector2 = #selector(SomeClass.swizzledOriginalMethod)
        let method1 = class_getInstanceMethod(SomeClass.self, selector1)!
        let method2 = class_getInstanceMethod(SomeClass.self, selector2)!
        method_exchangeImplementations(method1, method2)

        let someClass = SomeClass()
        someClass.originalMethod()
    }
}

class SomeClass {
    @objc dynamic func originalMethod() {
        print("I am the original method")
    }
}

extension SomeClass {
    @objc func swizzledOriginalMethod() {
        print("I am the replacement method")
    }
}

Running the above code, we can confirm that the swizzledOriginalMethod is invoked when calling the originalMethod

I am the replacement method

Modern method

Recently, we have added @_dynamicReplacement annotation to support native way to implement method swizzling in Swift.

Consider the following code in ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let someClass = SomeClass()
        someClass.originalMethod()
        someClass.orignalMethodWithParameters(for: "Param1", param2: "Param2")
        print(someClass.originalMethodWithParametersAndReturnType(param: "Param"))
    }
}

class SomeClass {
    dynamic func originalMethod() {
        print("I am the original method")
    }

    dynamic func orignalMethodWithParameters(for param1: String, param2: String) {
        print("I am the original method with parameters - param1: \(param1) - param2: \(param2)")
    }

    dynamic func originalMethodWithParametersAndReturnType(param: String) -> Bool {
        print("I am the original method with parameter - param: \(param) - and return type: Bool")
        return true
    }
}

When running the above code, we will see the following input

I am the original method
I am the original method with parameters - param1: Param1 - param2: Param2
I am the original method with parameter - param: Param - and return type: Bool
true

We prepare our SomeClass to be swizzleable by adding dynamic keyword into the to-be-swizzled methods.

Now adding the following code to replace the original methods with new implementations

extension SomeClass {
    @_dynamicReplacement(for: originalMethod)
    func replacementMethod() {
        print("I'm the replacement method")
    }

    @_dynamicReplacement(for: orignalMethodWithParameters(for:param2:))
    func replacementMethodWithParameters(param1: String, param2: String) {
        print("I am the replacement method with parameters - param1: \(param1) - param2: \(param2)")
    }

    @_dynamicReplacement(for: originalMethodWithParametersAndReturnType(param:))
    func replacement_withParameters_andReturnType(param: String) -> Bool {
        print("I am the replacement method with parameter - param: \(param) - and return type: Bool")
        return false
    }
}

When running the app again, we will see the following output in the console:

I'm the replacement method
I am the replacement method with parameters - param1: Param1 - param2: Param2
I am the replacement method with parameter - param: Param - and return type: Bool
false

As we can see, the method implementations in the extension have replaced the original method implementations of SomeClass without us subclassing SomeClass.

Pros and Cons

Like any technical solutions, when considering method swizzling, it’s crucial to weigh the benefits against the potential risks and maintainability concerns. It should be used as a last resort when other approaches are not feasible or practical.

  • Method swizzling can be powerful but also risky if not used carefully. It modifies the behavior of existing methods, which can lead to unexpected side effects or conflicts with other parts of the codebase.
  • Swizzling should be used judiciously and only when necessary. It’s important to thoroughly test and ensure that - swizzling doesn’t introduce bugs or performance issues.
  • Swizzling can make the codebase harder to understand and maintain, as the behavior of methods may not be immediately apparent.

If you do use method swizzling, it’s important to follow best practices, such as:

  • Swizzling methods only within your own classes or categories to avoid conflicts with other code.
  • Using unique method names to prevent naming collisions.
  • Ensuring thread safety when swizzling methods.
  • Documenting and commenting the swizzled methods clearly.

Alternatives:

If possible, you should consider the following alternatives to Swizzling:

  • Subclassing: If possible, subclassing and overriding methods is often a safer and more maintainable approach.
  • Composition: Using composition and wrapping objects can provide similar benefits without modifying the original class.
  • Dependency Injection: Injecting behavior through protocols or delegates can allow for flexibility without runtime modifications.
Quick Drop logo

Profile picture

Personal blog by An Tran. I'm focusing on creating useful apps.
#Swift #Kotlin #Mobile #MachineLearning #Minimalist


© An Tran - 2024