(This post was adapted from an email I sent to Mike Solomon).

Objective-C has a method caching mechanism that optimizes for the case where a small number of methods are called repeatedly on one (or more) objects of a given class. This happens very often; for example, if you iterate over an array to gather the results of performing an operation on each element, you’re going to be calling the same method on a bunch of instances of the same class. And in fact the -[NSEnumerator nextObject] method itself will also be cached.

The fact that method caching exists is common knowledge. However, what isn’t generally known is how that caching is implemented, and what it means for you if you want to hack around on the internals of a class.

Objective-C Method Caching

The biggest reason why people might care about Objective-C method caching is if they’re doing what’s commonly termed “Method Swizzling”. This is where you take two methods with the same signature (same return value and argument types), and you swap them so any code that tries to call the first will really call the second, and vice versa. The primary use of this is when you want to patch an app you have no control over - you can use a category to add a new method to the class, then swizzle that with the original method.

The reason why this is a potential issue is, since nobody knows how the method caching actually works, nobody knows if Method Swizzling can be affected by the method caching.

Well, the short answer is no.

Objective-C method caching works via a miniature hash table (of type struct objc_cache) stored in the cache field in every struct objc_class. This hash table contains pointers to recently-used Method objects, which are the same Method objects as found in the struct objc_method_lists on each struct objc_class. The important part here is the cached Method objects on a given class can actually belong to one of the class’s superclasses. This is what makes the caching really useful — the method dispatch system doesn’t have to trawl upwards through the class hierarchy to find the method if it’s already been cached.

The reason why Method Swizzling doesn’t have to worry about method caching is because of the fact that the cached Method objects are the same ones from the struct objc_method_lists in each class. When Method Swizzling modifies the Method object, the cache is modified at the same time, so any subsequent cache hit will reflect this modified Method.

If for some reason you want to flush the cache for a class (and I can’t think of why), simply insert

void _objc_flush_caches(Class cls);

somewhere in your file and then call it, passing the class you wish to flush the cache of. This particular method will also flush the cache of every superclass of the given class.

Method Swizzling

If you want to use Method Swizzling in your own app (or Input Manager, or SIMBL plugin, or…) the following implementation is the best one I’m aware of.

void PerformSwizzle(Class aClass, SEL orig_sel, SEL alt_sel, BOOL forInstance)
{
    // First, make sure the class isn't nil
    if (aClass != nil) {
        Method orig_method = nil, alt_method = nil;

        // Next, look for the methods
        if (forInstance) {
            orig_method = class_getInstanceMethod(aClass, orig_sel);
            alt_method = class_getInstanceMethod(aClass, alt_sel);
        } else {
            orig_method = class_getClassMethod(aClass, orig_sel);
            alt_method = class_getClassMethod(aClass, alt_sel);
        }

        // If both are found, swizzle them
        if ((orig_method != nil) && (alt_method != nil)) {
            IMP temp;

            temp = orig_method->method_imp;
            orig_method->method_imp = alt_method->method_imp;
            alt_method->method_imp = temp;
        } else {
#if DEBUG
            NSLog(@"PerformSwizzle Error: Original %@, Alternate %@",(orig_method == nil)?@" not found":@" found",(alt_method == nil)?@" not found":@" found");
#endif
        }
    } else {
#if DEBUG
        NSLog(@"PerformSwizzle Error: Class not found");
#endif
    }
}

void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
{
    PerformSwizzle(aClass, orig_sel, alt_sel, YES);
}

void ClassMethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
{
    PerformSwizzle(aClass, orig_sel, alt_sel, NO);
}

This particular implementation swaps the method_imp pointers on the two methods, so calling the first method will call the original implementation of the second, and vice versa. The elegant aspect to this is when you want to call the original method — simply call your new method again. The following example, adapted from SafariSource, demonstrates this.

- (void) mySetString:(NSString *)string {
    NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
    if ([defs boolForKey:SafariSourceEnabled]) {
        // do stuff here
    } else {
        [self mySetString:string];
    }
}

When any code tries to call setString: on this class, the swizzled method mySetString: is called again. But since the swizzling is done in both directions, trying to call mySetString: will in fact call setString:. So when the code appears to be calling itself recursively, it’s really calling the original function.

The biggest flaw with this implementation (and any others I’ve seen) is that if you want to swizzle a method that’s actually implemented in a superclass, the swizzling will affect all instances of that superclass instead of just instances of the subclass. There are a couple of possible solutions, such as dynamically creating a new class and using it to pose as the old class, but none have yet been implemented to my knowledge.

Comments are closed.