Due to some reasons I have to keep original code without modification, but try to redirect system API into my code and then call back to the original one. For example, I would like to do something more in [NSString stringWithFormat:]
.
Now I try to use method swizzling. But it seems that NSString
is not loaded during the runtime of main, I move swizzling method to MyAppDelegate
. After that class_getInstanceMethod([NSString class], @selector(stringWithFormat:)
) is not nil. However, the swizzling method is still not working because class_getInstanceMethod([NSString class], @selector(override_stringWithFormat:))
is still nil, how should I fix that?
Thanks, clu
@interface NSString (MyNSString)
+ (id)stringWithFormat:(NSString *)format, ...;
@end
@implementation NSString (MyNSString)
+ (id)stringWithFormat:(NSString *)format, ... {
//do something...
[NSString stringWithFormat:format];
}
@end
Here is the code in MyAppDelegate
#import "MyNSString.h"
-(void) MethodSwizzle:(Class)c replaceOrig:(SEL) origSEL withNew:(SEL) overrideSEL {
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
if(class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)))
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, overrideMethod);
}
- (BOOL) application:(UIApplication*) application didFinishLaunchingWithOptions:(NSDictionary *) options {
...
//Unit test
NSString *a=[NSString override_stri开发者_运维知识库ngWithFormat:@"Test"]; //returned something
Method b = class_getInstanceMethod([NSString class], @selector(override_stringWithFormat:)); //return nil;
//do something...
[self MethodSwizzle:[NSString class] replaceOrig:@selector(stringWithFormat:) withNew:@selector(override_stringWithFormat:)];
}
Not sure what you're trying to do, the question is a little bit unclear. Also, don't try to inherit from NSString, it's part of a class cluster so it's a lot more complicated that just overriding one method. You even have to implement the storage yourself. Gets complicated real fast.
There are two ways of adding methods to an existing class in Objective-C: Categories and method Swizzling.
Categories allow you to define a new method on an existing class, you cannot reliably replace a method using categories. For example:
@interface NSString (MyStuff)
- (id)mySpecialInit;
@end;
@implementation NSString (MyStuff)
/* implementation of -(id)mySpecialInit would go here */
@end
Again, this method will not reliably replace an existing method on a class.
For that you need method swizzling. Caveat - this is an advanced feature of the runtime, and it's real easy to get yourself into a debugging nightmare, so use with caution. Swizzling allows you to replace the implementation of one method with another method of the same signature. Using some tricks, you can call the super implementation as well. Matt Gallagher is generally credited with having the best implementation of method swizzling, as it allows for reliably calling the pre-existing implementation (called supersequent implementation). Take a look at the linked article on Cocoa with Love for example usages. Your edited code probably doesn't work because you're trying to swizzle a class method, but using the getInstanceMethod function. Use class_getClassMethod() instead. Also, you need to rename your category stringWithFormat method, it must not clash with the real method name. One last caveat - I've never seen method swizzling used on a method with variadic arguments (...), and this C q&a leads me to believe this won't be possible (try this post as a possible solution). As an academic exercise, here is what it would probably look like:
NSString+MyMethods.h
@interface NSString (MyMethods)
+ (id)clu_stringWithFormat:(NSString *)format,...;
@end
NSString+MyMethods.m:
@implementation NSString (MyMethods)
+ (id)clu_stringWithFormat:(NSString *)format,... {
//Do your stuff here
//call supersequent implementation
//again, I have no idea if this will work as is, you might need to tweak how it passes the variable argument list
invokeSupersequent(format);
}
+(void)load {
//do your method swizzle in here, this is called one time only
Method origMethod = class_getClassMethod([NSString class], @selector(stringWithFormat:);
Method replMethod = class_getClassMethod([NSString class], @selector(clu_stringWithFormat:);
method_exchangeImplementations(origMethod, replMethod);
}
Given that this is likely impossible, and is a very bad idea otherwise - you're changing how a specific class in a class cluster operates, which Apple says "don't do this". And for good reason - what happens if someones calls [NSMutableString stringWithFormat:], or allocs an NSString and then calls -initWithFormat:? There are a lot of valid paths into creating a string with a format, you'd have a miserable time trying to maintain code which intercepts all of them. I would encourage you to rethink the design of this - whatever problem you're trying to solve, there is likely a much easier and more maintainable way of achieving it. Adding your own String Factory, for example.
精彩评论