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