While writing some test Swift code, I ran into a situation where I wanted the implementation of a class method swapped out with some fake code. Basically, I wanted to swap out SomeClass‘s saveSomeThings class method with a fake one.
Inspired by NSHipster’s Swift & the Objective-C Runtime article, I came up with some concise code that does what I want. Focusing on only what shows the concept, we have the following example SomeClass:
1 2 3 4 5 6 7 |
class SomeClass { dynamic class func saveSomeThings(someThings: [String]) { print("real code getting called") } } |
The saveSomeThings method implementation is what we want to swap out with some fake code for testing purposes. How, you might ask? First, ensure the method has the dynamic keyword as discussed in this Stack Overflow post. Next, we grab a handle to the class method like so:
1 |
static let originalMethod = class_getClassMethod(SomeClass.self, #selector(SomeClass.saveSomeThings(_:))) |
Now we need something to swap it with. What if we had a method inside a SomeTestClass class like the following just for testing purposes?:
1 2 3 4 |
dynamic class func forTesting_SaveSomeThings(someThings: [String]) { print("forTesting_SaveReportCategories called") } |
We can grab a handle to the method like so:
1 |
static let swizzledMethod = class_getClassMethod(SomeTestClass.self, #selector(SomeTestClass.forTesting_SaveSomeThings(_:))) |
The actual call to swap the implementation between the saveSomeThings and forTesting_SaveSomeThings is done using method_exchangeImplementations like this:
1 |
method_exchangeImplementations(SomeTestClass.originalMethod, SomeTestClass.swizzledMethod) |
The SomeTestClass prefixes the originalMethod and swizzledMethod because I am storing them as static members of the SomeTestClass.
Those are all the magical pieces of this puzzle! Besides being on github, here is a complete Swift playground example to see it all in context:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
//: Playground - noun: a place where people can play import Foundation class SomeTestClass { dynamic class func forTesting_SaveSomeThings(someThings: [String]) { print("forTesting_SaveReportCategories called") } static let originalMethod = class_getClassMethod(SomeClass.self, #selector(SomeClass.saveSomeThings(_:))) static let swizzledMethod = class_getClassMethod(SomeTestClass.self, #selector(SomeTestClass.forTesting_SaveSomeThings(_:))) func someTestMethod() { // swapped out the real code with the fake code method_exchangeImplementations(SomeTestClass.originalMethod, SomeTestClass.swizzledMethod) SomeClass.saveSomeThings(["some Things", "and more"]) // put the real code back method_exchangeImplementations(SomeTestClass.originalMethod, SomeTestClass.swizzledMethod) SomeClass.saveSomeThings(["some Things", "and more"]) } } class SomeClass { dynamic class func saveSomeThings(someThings: [String]) { print("real code getting called") } } let runTest = SomeTestClass() runTest.someTestMethod() |
At CARFAX, I have been fortunate enough to work on such interesting challenges like the one above in Swift, Objective-C and Java based languages. Since CARFAX is a growing company with opportunities that open up from time to time, you might want to checkout the CARFAX Career link.
Regardless, I hope you found the above information interesting and useful. Enjoy!