What I wanted to do was this:
+ (void)load { Method foo = class_getInstanceMethod(self, @selector(foo)); Method newFoo = class_getInstanceMethod(self, @selector(FZA_swizzleFoo)); method_exchangeImplementations(foo, newFoo); }
However, my tests wouldn’t work when I did that. It turns out that for some reason +load was running twice, so the methods got swizzled twice meaning that each implementation ends up married to its original selector. So I thought I’d do this:
+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method foo = class_getInstanceMethod(self, @selector(foo)); Method newFoo = class_getInstanceMethod(self, @selector(FZA_swizzleFoo)); method_exchangeImplementations(foo, newFoo); }); }
…and, *drum roll*, that doesn’t work either. What gives?
Well, it turns out that the reason that the category was being loaded twice is because it was included in two separate binary images: the library I was testing and the unit test bundle. Because of this, the “static” dispatch_once_t instance was actually a separate instance for each image, so dispatch_once() couldn’t tell it had already run the block.
In my case there’s a solution; I only need the category to be loaded once so removed it from one of the targets. But the general message is that category loading is very non-deterministic, and hard to rely on. Imagine if you rely on an open-source category, then one of your plugin developers relies on the same category. Or one of the frameworks your app links against. There are specific cases where the implementation of categories causes no effects, or effects that are understood and may be worked around, but those are really the exceptions.
+ (void) load {} can often run more times than you expect – eg. if you use KVC on that class.
And the title is misleading – categories aren’t swizzling. The fact that swizzling can bite you should surprise noone. Did a fake Graham Lee write this blog post ? :)
Mark, I’ve certainly had bugs where +initialize ran multiple times in KVC environments, so I assume that +load can indeed suffer that problem. That one’s easy to solve though:
+ (void)initialize {
if (self == [MySpecificClass class]) {
// …
}
}
By the way, I stand by my title. My swizzling does what I expect (though it does swizzle some initialisers, which is a bit worrying), it’s the category loading mechanism that causes it to break.