简体   繁体   中英

Method Swizzling isEqualToString

I'm running into some odd behavior when trying to Method Swizzle isEqualToString: on the NSString class. Here is the code in question:

#import <Foundation/Foundation.h>
#import <objc/objc-runtime.h>

@interface NSString (SwizzleString)
- (BOOL) custom_isEqualToString:(NSString *)aString;
- (NSRange)custom_rangeOfString:(NSString *)aString;
@end

@implementation NSString (SwizzleString)

- (BOOL) custom_isEqualToString:(NSString *)aString;
{
    NSLog(@"Inside custom_isEqualToString method definition");
    return [self custom_isEqualToString:aString];
}

- (NSRange)custom_rangeOfString:(NSString *)aString;
{
    NSLog(@"Inside custom_rangeOfString method definition");
    return [self custom_rangeOfString:aString];
}

@end

int main(int argc, const char * argv[])
{

    Method m1, m2;

    m1 = class_getInstanceMethod([NSString class], @selector(isEqualToString:));
    m2 = class_getInstanceMethod([NSString class], @selector(custom_isEqualToString:));
    method_exchangeImplementations(m1, m2);

    m1 = class_getInstanceMethod([NSString class], @selector(rangeOfString:));
    m2 = class_getInstanceMethod([NSString class], @selector(custom_rangeOfString:));
    method_exchangeImplementations(m1, m2);

    NSString *foo = @"Foo";

    // Does not log anything, is still using isEqualToString: implementation
    [foo isEqualToString:@"Foo"];
    // Also does not log anything, since it is using the method implementation from isEqualToString:
    [foo custom_isEqualToString:@"Foo"];

    // Does log something because rangeOfString now uses custom_rangeOfString IMP
    [foo rangeOfString:@"Foo"];
    // Does not log anything because it uses the method implementation from rangeOfString:
    [foo custom_rangeOfString:@"Foo"];
}

isEqualToString: and rangeOfString: are both defined in a category on NSString called (NSStringExtensionMethods) , so I included the rangeOfString swizzle to show that I'm swizzling methods correctly and specifically an NSString object successfully so I could eliminate questions about class cluster problems.

When I produce the assembly for the code above, instead of seeing normal objc_msgSend calls I instead see stuff like l_objc_msgSend_fixup_isEqualToString_ . This led me to finding out more about the objective-c vtable , in which it seems isEqualToString: can be found:

static const char * const defaultVtable[] = {
    "allocWithZone:", 
    "alloc", 
    "class", 
    "self", 
    "isKindOfClass:", 
    "respondsToSelector:", 
    "isFlipped", 
    "length", 
    "objectForKey:", 
    "count", 
    "objectAtIndex:", 
    "isEqualToString:", 
    "isEqual:", 
    "retain", 
    "release", 
    "autorelease", 
};

I've been digging through the objective-c source and the internet all day on how it would be possible to somehow still be able to swizzle isEqualToString: .

As you're aware, this is a class cluster. You need the actual class, not the public class, so just ask the string object that you have:

NSString *foo = @"Foo";

m1 = class_getInstanceMethod([foo class], @selector(isEqualToString:));
m2 = class_getInstanceMethod([foo class], @selector(custom_isEqualToString:));
method_exchangeImplementations(m1, m2);

There's also the fragile alternative of using the class name itself:

NSClassFromString(@"__NSCFConstantString")
NSClassFromString(@"__NSCFString")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM