Method Swizzling | Swift
How to change an object behavior in Runtime
First things first
Before the explanation about Method Swizzling, it’s important to understand what is the Swift Runtime, once it’s a key concept that enables language features like the Swizlling.
Runtime
The Swift Runtime is a part of the standard library that enables behaviors while the application is running. Good examples of Those behaviors are casts, reflections, and memory allocation.
At this moment developers start to realize that those behaviors are pretty common in the development. Casts, for example, are present almost every time inheritance is used, transforming superclasses into subclasses.
But how those things are Runtime features?
All the examples given are very common, for example, a cast, that is basically the keyword as
in the code. But this is what a cast represents only in the compiling time, which leads to the next important concept.
Runtime X Compile time
In development, in almost all languages, the concept of those moments is very important in an application
The Compile time is the moment where the code is written and compiled, which means is the time where the code is verified to check if it’s following the lexical, syntax and semantic rules of the language.
The Runtime happens after the compiling time when the code is running, which means the code is right in terms of language rules and we can interact with it given inputs for example. Besides the interaction possibility, during the runtime, the application also deals with a lot of responsibilities, like allocate memory to variables, structs, classes, and so on.
Memory allocation
As said previously, one of the responsibilities of Runtime is Memory allocation, that is the process to separate parts of the device memory so that it is possible to save information, like a class and its properties values and methods. A good way to understand this is by looking at how it is done by the application, which can be represented by a memory table.
The following example shows a memory table of a class named SomeClass
, this class has a property named value
of type Int
and a method named foo
.
A Memory table usually has two columns, the first representing the memory address, and the second one the value that is saved in this address.
In this example the property value
is in the address #110
and has saved the Int
value 5000
. To represent a method, there is the address #111
that has the value #230
, which is an address and that address has the value 100
, that can be assumed as the foo
implementation.
The address #111
can be called Pointer. A pointer is a really important concept in languages like Swift, which is a C
based language.
A pointer is an address that holds another address and so on. As addresses values are mutable during runtime, a pointer can point to different addresses, with different values.
Memory allocation in Swift — Going deeper
In Swift, during the compile time, the Swift compiler makes it easier to — during Runtime — allocate memory to new instances of some classes.
To do this, the compiler creates a Dispatch Table, this table is created when an object is loaded in memory, which is different from instantiating an object.
- Load: An object is loaded in the first time it’s created. It’s important to notice that it only happens once and only to objects exposed to the Objective-C compiler (it’s possible by making the class inherit from
NSObject
) since the greater power of Runtime comes from it. - Instantiate: Every time an object is created it’s also instantiated, which means, a new part of the device memory will be separated to save this object. If the object is exposed to the Obj-C to separate the memory, it will use a Dispatch Table, as like a template, to know how much of memory will be separated and what values, it will save.
Apple’s documentation explains how it works with the methods +load
and +initialize
.
Dispatch Table
The Dispatch Table is created to help the application instantiate new instances during Runtime in a faster way. The following example is a dispatch table from a class named SomeClass
This table represents a class by its Methods
. A Method
is a C struct
with the properties Selector
and Implementation
.
The Selector
is a C string
that represents the method name, for example, if a class named SomeClass
has a method
named foo
— func foo()
— its selector will be "SomeClass.foo"
.
The Implementation
is a pointer to the code instructions that the method executes when called.
Method Swizzling
After all the previous concepts, understand the Method Swizzling is easy.
Method Swizzling is a feature enabled by the Runtime that changes the behavior of every single instance of some class without the use of inheritance or extension. It basically switches two methods implementations.
This change is possible in Swift because of the Dispatch Table and the Pointers. The next example shows what a swizzle does in the dispatch table to enable this change of behavior in Runtime.
The example shows a Swizzle between "SomeOtherMethod"
and "SomeNMethod"
which means an exchange between the implementations pointer #220
and #300
. This is possible because a pointer can change where it is pointing during Runtime.
Code
Although the concept looks easy after the explanation the code can be a little scary but is just because it follows the C
functions name pattern.
To make a Swizzle it’s necessary to access the dispatch table properties and exchange the IMP
of two methods.
First, it’s necessary to find in which table the swizzle will happen, in other words, which class will have the behavior exchanged. There are some ways to find it, in the snippet below are some o them.
Second, it’s necessary to know which methods will exchange their implementations. The best way to find a Method
is through its name, that is the Selector
.
It’s possible to get the Selector
by its name. The following examples show how to do it.
With the class type and the method selector — that represents the dispatch table and one value in column SEL
respectively —it’s possible to get the Method
, which is the full line in the table.
To get the Method
use the function class_getInstanceMethod
.
Since the Swizzling is an exchange, all this process must happen twice.
With all this information from the dispatch table, it’s possible to make the swizzle.
It’s possible to encapsulate the swizzle in a static function as following.
With this is possible to change every instance — of any class that inherits from NSObject
— implementation between two methods.
When to use a Method Swizzle
With the theory and the code, the only thing left is to know where to use this technique.
As the Swizzling changes every instance implementation it’s a global change, so its use should take it into account. So it’s recommended to use the swizzle when all instances of some class should do something and inheritance is not an option, or when it must change a class behavior in an extension, but the function can’t be overridden.
To see a real example take a look in the below Playground.
After running the playground it’s possible to notice that the Swizzle is an important feature and very powerful, but as uncle Ben said.
“With great power comes great responsibility”
Although this feature of Runtime can enable a lot of possibilities it can cause problems too, like bug tracking, once it will change the behavior of the application during runtime, and in big projects, it can be a nightmare to control, since it will affect all the project.