Fo' Swizzle

It is said that necessity is the mother of invention. Granted, I haven’t invented anything, however necessity did drive my latest solution.

Vendors – PIA

While working on a project for work I have the unfortunate requirement of using one of our vendor’s iOS frameworks that is horribly written and documented. From day one I discovered bugs and inconsistencies with how it is implemented. For one case I ended up reverse engineering what they were doing and sent them the snippet they should add in to fix the problem.

Not Wasting Time

Getting vendor to make changes that I needed, and consequently, the company, is about as quick as standing in line at your local DMV. I had to move the project along as fast as possible. During the early stages of the project I picked up a copy Graham Lee’s Test Driven iOS Development and took his advice on how to make my unit testing much more efficient with better coverage…so I started writing as many mock objects as I could for unit testing. In order to handle future test cases I created a MockObjectDataManager, but I found myself writing a lot of if/else statements throughout the app itself to handle switching between dev/test/stage/production web services vs. mockobjects. This was not good.

What I needed to do was setup the app so that any other developer, including myself, only had to write calls to the vendor’s framework, but could switch easily to using mockobjects when appropriate.

Method Swizzling to the rescue!

Fo’ Swizzle

The What

Unlike creating a category method with the same name as the original method (effectively replacing the original method), MethodSwizzling lets your replacement method make use of the original method, almost like subclassing.

Source: CocoaDev

Danger Will Robinson, Danger

I have read and seen developers using this technique, but it usually followed up with disclaimers such as:

  • unstable
  • experimental only
  • use at your own risk

Because of this general attitude I had seen I shy’d away from every really using this runtime feature. While I was foolish early on in my iOS development career to not learn more about it, using it without serious thought and consideration would probably proved distasterous. Granted, swizzling does allow a developer to swap methods for their own implemenations, sed developer has to make a lot of assumptions about the implemenation of the original method. If that implemenation changes the likelihood for your replacement method to break increases.

Necessity

As stated earlier myself and my team needed the ability to rely on the vendor framework for communication to and from the web services, but also needed the ability to writing unit tests and CRUD mockobjects WITHOUT cluttering the app code base.

First, I recreated the json response from each endpoint and saved those into static files. Second, I rewrote the mock object manager to access and cache the models asynchronously, with better error handling. Third, I added in a preprocessor macro to flag whether or not the swizzled method should be used. Finally, added in a convience method to handle swizzle.

I hit Cmd+r and was off to the races. The app crashed. Had I been over confident in my ability to leverage the almighty runtime. Well, no…I just needed to pay attention to how classes are loaded. As I stated earlier, my mockobject data manager loads the .json files asynchronously. When the swizzled method was called the dispatch_queue was NULL and resulting in a BAD_ACCESS error.

iFixit

The first attempt was to check for existence of the queue in the loadLoginResponse method:

if (!_fileQueue) { _fileQueue = dispatch_queue_create(fileQueueName, 0); } But wait…I had already instantiated _fileQueue in the sharedManager class method. Didn’t seem very reusble. As to not be out done this far into it I remembered the lifecycle of how/when classes are loaded and most importantly which the order of the meta methods. I moved the property and check from both the sharedManager and loadLoginResponse methods to + (void)initialize. Setting the USEMOCKOBJECTS to 1 and rerunning the app I successfully loaded my mock objects.

Lessons Learned

The most important lesson learned is to not be afraid to experiment with the more advanced features of Objective-C. When used correctly they provide a lot of flexibility and benefits to development and testing. My app is now setup to give any developer the ability to test against any live enviornment, as well as, test out new/existing features without changing a 3rd party framework or cluttering the codebase with a bunch of unnecessary “if/else” statements.

A great example of using these types of techniques is PonyDebugger from a little company called Square.

Get the Gist

gist.github.com/3762103