Has Apple blocked Method Swizzling in iOS 5?
I was doing a little playing around and discovered that an app with Method Swizzling works on iOS 4 but not on iOS 5.
Apple sent an email a while ago to some devs that were found to be using method swizzling in App Store apps:
Your application, xxx, currently posted to the App Store is using method_exchangeImplementations to exchange the implementation of Apple provided APIs with your own implementations. Because of upcoming changes, this behavior in your application may cause a crash or cause user data loss on iPhone OS 4.0.
xxx uses method_exchangeImplementations to exchange the implementation of dealloc with your method ttdealloc. It also exchanges the implementation of the method popViewControllerAnimated: with your method popViewControllerAnimated2:.
Please resolve this issue immediately and upload your new binary to iTunes Connect. We may remove your application if we believe that doing so is prudent or necessary.
Looks like they wanted to get rid of it, so I'd say chances are pretty high that they've now blocked it completely.
UPDATE: (My App uses this method and is in the appstore)
Method swizzling seems to be working as of May 30, 2012. this is my implementation.
(This is for those of you looking around and finding bad code on wiki pages and just want a quick implementation.)
Swizz.h
#import <Foundation/Foundation.h>
void ActivateAutoSwizz();
void Swizz(Class c, SEL orig, SEL replace);
@interface NSObject (swizz)
// This Method allows the class to Swizzle more methods within itself.
// And allows for an overridable init method in Class Extensions
// ######################
// //// To Swizzle a method, call Swizzle once on the class in question.
// //// dispatch_once is a good way to handle that.
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
// Swizz([UITableViewCell class], @selector(reuseIdentifier), @selector(classReuseIdentifier));
// });
- (void) swizzInit;
@end
Swizz.m
#import "Swizz.h"
#import <objc/runtime.h>
#import <objc/message.h>
//....
void Swizz(Class c, SEL orig, SEL replace)
{
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, replace);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, replace, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
@implementation NSObject (swizz)
// Load gets called on every object that has it. Off Thread, before application start.
+ (void) load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Swizz([NSObject class], @selector(init), @selector(initIntercept));
});
}
- (id) initIntercept{
self = [self initIntercept]; // Calls the Original init method
if(self){
[self swizzInit];
}
return self;
}
- (void) swizzInit{
//Do Nothing.. Gives an extension point for other classes.
}
@end
I built this to allow me to intercept the reuseIdentifier on the UITableViewCell with a UITableViewCell extension.
Here is that example.
UITableViewCell+ReuseIdentifier.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@interface UITableViewCell (ReuseIdentifier)
@end
UITableViewCell+ReuseIdentifier.m
#import "UITableViewCell+ReuseIdentifier.h"
#import "Swizz.h"
@implementation UITableViewCell(ReuseIdentifier)
// Edited to remove Class variable.
// Class variables in Categories are Global.
// And as it turns out, this method did not need the variable anyhow.
- (NSString *)classReuseIdentifier{
NSString *reuseIdentifierResult = [self classReuseIdentifier]; // Gets the original reuseIdentifier
if (reuseIdentifierResult == nil){
reuseIdentifierResult = [[self class] description];
}
return reuseIdentifierResult;
}
// Alternatively you can use the +(void)load method on the class to achieve the same thing.
- (void)swizzInit{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Swizz([UITableViewCell class], @selector(reuseIdentifier), @selector(classReuseIdentifier));
});
}
@end
As you can see, the ActivateAutoSwizz() as well as my swizzInit method both use dispatch_once to execute the swizzle once.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Swizz([NSObject class], @selector(init), @selector(initIntercept));
});
If you execute it twice. it reverses your method switch back to original. I hope this helps some of you iOS devs out there.
NOTE: I have determined that +(void) load is called once at app start and is a wonderful place to achieve the method swizzle. Unfortunately in some dev situations +(void)load is not called, You may want to test your app to make sure these methods are being called.
Well, we received the OK about a mont hago (beggining of May 2012) for an app that made a lot of use of method Swizzling to customize standard UI components in iOS4 (iOS5 using appearance). Besides, method swizzling is a fully documented API that also gives very powerful features not related to Apple itself or the use of private APIs. I find it difficult to believe that they could reject such a thing!
Anyway, please, keep everyone informed if you see more rejections related to this! Thanks!