When anyone mentions swizzling I automatically get worried. Method swizzling is an Objective C runtime feature which allows for the switching of method implementations. This means any function can theoretically be switch out for another one at run time. The Objective C runtime as a whole host of runtime features to modify classes, methods, objects, properties etc. We’re going to look at method swizzling, which as mentioned before, is the process of changing a function implementation for another one at runtime.
So is Swizzling really a bad thing?
If not done correctly swizzling can open a whole host of issues.
Harder to debug – As we are swapping method implementations out, this can make debugging slightly harder. If you pick up a new project and don’t know a certain method has been swizzled, it can be quite hard to follow the implementation addresses in the backtrace.
Anyone can swizzle – As a long as the class is an Objective C type its method can be swizzled. Anyone is free to change the code, as long as they know the class, and the name of the selector. This means any framework could modify parts of your app without you even knowing. It also means if you are swizzling a function, and another framework is swizzling the same function, there is going to be a mismatch, and only one implementation will be used.
Confusing – For anyone used to programming with swift and using the swift APIs, the Objective C runtime can be quite confusing, and hard to read. Having to get methods and implementations to swap around is not something that is done very often.
When to use Swizzling
The majority of the time I would say that swizzling should not be your first option, and that your problem could most likely be solved another way. However there is one use case I’ve come across which I feel is a good candidate for method swizzling.
App Delegate Swizzling for Framworks
Frameworks, be it 3rd party or local frameworks, have no knowledge of the app lifecycle. They do not get application events such as applicationWillEnterForeground, didFinishLaunchingWithOptions, handleEventsForBackgroundURLSession, etc. Frameworks do have access to the notification centre where they can subscribe to, and listen to changes to many of the application functions. However notifications are not sent with all of them. For example push notification configuration, and background fetching, do not send notifications when their respective functions have been called. So what happens when a framework needs to modify an app delegate function, but has no way to see the function.
Manually pass values in
The user could manually pass the values into the framework when the relevant app delegate method is fired. The example below shows an app delegate, and a framework class which would live inside a completely separate module.
Although this way of engineering does solve the problem, it does mean that everyone who adopts the framework will have to manually pass these values through.
It would be better if the user didn’t have to do this extra step, and the framework just handled the logic on its own. This is where method swizzling comes in. By swizzling the app delegates handleEventsForBackgroundURLSession function, the framework can take care of this logic without the user having to do anything.
The framework will now swizzle the implementation of handleEventsForBackgroundURLSession from the app delegate to an implementation of a method from inside the framework.
A few thing to note here.
First, it creates the selectors by getting a reference to the app delegate, and newly created function within the Framework. It then checks the app delegate to see if handleEventsForBackgroundURLSession: is already visible. If the user has already implemented handleEventsForBackgroundURLSession: then it has an implementation to swap, otherwise it will add the method to the app delegate using class_addMethod. Lets take a look at the two key swizzling functions in more detail.
This switches the implementation of two Method types. These Method types are pulled from the selectors created at the beginning, along with their respective class types.
class_addMethod(type(of: delegate), selector, method_getImplementation(swizzled), nil)
If the method implementation does not exist within the app delegate, then it will have nothing to swizzle. So in this instance a new method is added to the app delegate, using the function above. It creates a new method from a selector and the implementation of handleEventsForBackgroundURLSession: which lives inside the framework.
Now whenever handleEventsForBackgroundURLSession: is called in the app delegate, it will always call the function inside the framework. This is all well and good but what happens when the user still wants to provide their own functionally, or another framework wants to access handleEventsForBackgroundURLSession.
For this case I would always provide a way for the user to disable swizzling. This way they can manually go back to importing the values without swizzling. This seems to be the adopted way of handling swizzling as is used in many 3rd party frameworks, Firebase, Airship etc.