In a recent episode of Hypercritical, Mssrs. Benjamin and Siracusa revealed the secret for disabling a system-wide window animation that Apple added in their most recent OS release. I’ve seen these sorts of tips (where you go to terminal and use a command to update a system-wide and undocumented preference value) before, but I never really gave much thought to how people discover these. If I had to guess, I’d say that some engineer at Apple told a few friends and then it spread.

But, it turns out that there’s a more methodical way to discover these things. And Siracusa dropped the clue in that podcast. He said that Mr. Franzén had swizzled some methods. “Ah hah!” I said. “So that’s the secret!” And then I said, “Well, duh. Why didn’t I think of that myself?! How else would it work?!”

But even, then, I only knew this secret in the abstract. I knew how to swizzle methods in Objective-C, but I’d never really used it for anything. So I decided to investigate. Allow me to drop some knowledge. I’ll start with some background.

The main application development language for OS X is Objective-C. Objective-C is a strict superset of the older language C with object support added to the C base in a backwards compatible fashion. One of the most interesting things about Objective-C is that it’s a dynamic language even though it’s also a compiled language (unlike the popular dynamic languages Ruby and Python which are both dynamic but are usually interpreted). This dynamic nature lets the language do some cool things. Indeed, by using things like Key-Value Observing, many developers take advantage of Objective-C’s dynamic nature without even realizing it. A lot of the things that Cocoa developers miss in other languages turns out to be a result of Objective-C’s dynamic nature.

Now, you know how Objective-C is built on top of C? This goes all the way down to the very core of the language. In the end, Objective-C objects are really just C structs and Objective-C methods are really just pointers to standard C functions. All of this is managed invisibly by the Objective-C runtime. But it doesn’t have to be invisible: the full power of the runtime is available to any application developer by importing <objc/runtime.h>. I’m going to use this power to swap out some Apple code with my own: but which code?

These secret preferences (and particularly the one I’m interested in at the moment) are usually given in the form of terminal commands which write a value to the system defaults database, the preference database system that Mac OS X provides. For Objective-C programs, Apple has provided the NSUserDefaults class for talking to this database. NSUserDefaults has several instance methods for retrieving defaults values like objectForKey:, stringForKey:, integerForKey:, etc. Since I already knew I was looking for NSAutomaticWindowAnimationsEnabled and this sure looks like a boolean value, I decided to start by swizzling boolForKey:.

First, of course, I need a fake boolForKey: to swizzle out. I’m going to implement this as a regular old C function and take advantage of the fact that every Objective-C method is really a standard C function with a hidden id and SEL parameter tacked on to the beginning of the parameter list. The id is the object that the method is being called on (in fact, when you access self in an Objective-C method, you’re really just using this hidden parameter!) and the SEL is the selector that was originally used to call the method.

I’ve also created a static IMP called realMethodImplementation_boolForKey. When I swizzle the methods, I’ll store the original in this variable so my fake method can call it to hand the real value back to the original caller. IMP is just a function pointer to a function that returns an id and has an id and SEL as the first two parameters, so I can call the realMethodImplementation_boolForKey just like I would a normal function.

BOOL fakeBoolForKey(id s, SEL cmd, NSString *defaultName)
{
    NSString *result = @"No real method was found"; //The string that we'll log to the console
    BOOL r = NO; //The value we'll return to the caller
    
    //If we cached an real method, call it (remember, IMP is just a function pointer). 
    if(realMethodImplementation_boolForKey) { 
        
        //IMP is typed to return an id, but we can cast that to BOOL to get the right value
        r = (BOOL) realMethodImplementation_boolForKey(s, cmd, defaultName);

        //Pretty-print whatever we got back from the real function
        result = r ? @"YES" : @"NO"; 
    }
    
    NSLog(@"boolForKey: %@ and got answer: %@", defaultName, result);
    return r;
}

Once I have my custom implementation, I can write a function to actually swap out Apple’s boolForKey: for mine. You can see here where I save the IMP to Apple’s function before swapping it out for mine with class_replacemethod(). The only other interesting thing here is that class_replaceMethod wants the method signature encoded as a char*. I think you should be able to do this with the @encode() directive, but I’m not sure how everything needs to line up. Since I already had the real method handy and my signature was identical, I just got the encoding from the real method and passed that in to class_replaceMethod.

void swizzleBoolForKey(void) 
{
    //We want to swizzle boolForKey: on NSUserDefaults, so get the basic metadata for those
    Class cls = [NSUserDefaults class];
    SEL selector = @selector(boolForKey:);
    
    //Convert the selector into a string so we can log which method we're swizzling
    NSString *selectorString = NSStringFromSelector(selector);
    
    //fakeBoolForKey(...) is the function we want to call instead of whatever Apple wrote. Turn it into 
    //an IMP (which is really a function pointer, so a simple cast will suffice)
    IMP newImplementation = (IMP) &fakeBoolForKey;
    
    //Lookup the method that Apple wrote
    Method realMethod = class_getInstanceMethod(cls, selector);
    
    //Save the function pointer for the method that Apple wrote so we can call it from the swizzled function
    realMethodImplementation_boolForKey = method_getImplementation(realMethod);
    
    //Try to replace Apple's IMP with ours. method_getTypeEncoding returns a char* encoding of the method's signature and 
    //parameters. Since we're swizzling out a method with identical parameters and signature, just get the type encoding from 
    //the real method. 
    if(!class_replaceMethod(cls, selector, newImplementation, method_getTypeEncoding(realMethod))) {
        NSLog(@"Could not replace %@", selectorString);
    } else {
        NSLog(@"Replaced %@!", selectorString);
    }
}

For my first pass, I had a little window with a “Swizzle!” button on it. Clicking the button would call my swizzleBoolForKey() function. And then I’d watch my console as…nothing happened. By the time I was able to click a button, the system had already loaded all of the preferences it needed. I had to get my swizzle in earlier.

So I did something you almost never do when writing a Mac app. I did something you’re not really even supposed to do. I opened up main.m and put my call to swizzleBoolForKey() directly before the normal NSApplicationMain() call. Once the swizzle was literally the first thing that happens in the program, I started seeing things fly by on the console as a bunch of boolForKey: messages got logged. But, despite seeing tons of fun settings (like NSOnlyFlipFontsWithIdentityMatrix, whatever that does), I didn’t see anything about window animations. Hmm.

Since I already knew what I was looking for, I knew it had to be there. But I wasn’t seeing it. It was hiding. So I decided that my guess that it would show up in boolForKey: was wrong. They must be doing something like retrieving it as an integer and seeing if it’s 1 or 0. Or something like that. I didn’t know, so I realized that I had to do something drastic. I’d have to swizzle all of the {type}ForKey: methods.

I didn’t want to write a new fake function for each of these methods (there’s 11 in all), so I decided that my second attempt would be a bit more generic. My new method would take advantage of the cmd argument to see which selector had been used to call it. It could then use that to both log which method it was pretending to be as well as look up the real implementation in a static NSDictionary (again, I’ll save it here when I actually swizzle the method). Since SEL is really a pointer and NSDictionary cannot store pointers, I have to store it as an NSValue.

Perhaps more importantly, I don’t really know what the return type will be (a float? A BOOL? An object? Who knows?) so I don’t have a good way to format the result. So I decided to just not output that.

id fakeAnythingForKey(id s, SEL cmd, NSString *defaultName)
{
    //Translate the selector to a string so we can log which method got called and 
    //so we can look up the real method implementation in our dictionary
    NSString *selectorString = NSStringFromSelector(cmd);
    
    NSLog(@"Called %@ for %@", selectorString, defaultName);
    
    //Look up the real implementation method in our dictionary using the selector string as key. 
    //IMP is a pointer, so we were able to store it in an NSValue
    NSValue *realImpValue = [realMethodImplementations valueForKey:selectorString];
    IMP realImp = NULL;
    
    //IMP is typed to return id
    id r = nil;
    if(realImp) {
        realImp = [realImpValue pointerValue]; //Get the function pointer value from the NSValue and call it
        r = realImp(s, cmd, defaultName);
    }
        
    //Since I don't know if r is really an object or a primative (were we objectForKey: or floatForKey:?),
    //I dont' want to log the output. If that were important, we could switch over the selector to format it
    //properly. Alternately, once we knew which method we were interested in, we could write a one-off swizzle 
    //like we did for boolForKey:
    return r;
}

I then knocked up a quick generic swizzling method. The most interesting thing here is where I shove the SEL into an NSValue (and that’s not particularly interesting).

void swizzleForKeyMethod(SEL selector) 
{
    //We want to swizzle selector on NSUserDefaults, so get its class metadata
    Class cls = [NSUserDefaults class];
    
    //Convert the selector into a string so we can log which method we're swizzling and use it as a 
    //dictionary key for saving the real IMP to call from the swizzled method
    NSString *selectorString = NSStringFromSelector(selector);
    
    //We want to replace Apple's method with a fakeAnythingForKey. IMP is a function pointer so a simple cast
    //will suffice
    IMP newImplementation = (IMP) fakeAnythingForKey;
    
    //Lookup the method that Apple wrote
    Method realMethod = class_getInstanceMethod(cls, selector);
    
    //Get the function pointer for Apple's method and save it to an NSValue. Remember to use the alloc-init
    //pattern to avoid creating an autoreleased object with the convenience methods
    IMP realMethodImplementation = method_getImplementation(realMethod);
    
    NSValue *realMethodImplementationValue = [[NSValue alloc] initWithBytes:&realMethodImplementation objCType:@encode(IMP*)];

    //Store the implementation of the real method into a global dictionary with the selector string as the key
    [realMethodImplementations setValue:realMethodImplementationValue forKey:selectorString];
    
    //Try to replace Apple's IMP with ours. method_getTypeEncoding returns a char* encoding of the method's signature and 
    //parameters. Since we're swizzling out a method with identical parameters and signature, just get the type encoding from 
    //the real method. 
    if(!class_replaceMethod(cls, selector, newImplementation, method_getTypeEncoding(realMethod))) {
        NSLog(@"Could not replace %@", selectorString);
    } else {
        NSLog(@"Replaced %@!", selectorString);
    }
}

And that’s it! As the first thing in main(), I can just call something like swizzleForKeyMethod(@selector(boolForKey:)) and I’ll have a swizzled method. Once I swizzled all 11 of the NSDefault {type}forKey: methods, I started seeing tons of stuff fly across my console. One of them (well, five of them for some reason) was an objectForKey: call for NSAutomaticWindowAnimationsEnabled. If I had been doing the original research, I could have done a quick Find for something like “anim” and I’d have found it. Then I could’ve been the big Lion hero instead of this Tomas Franzén guy. Alas. I don’t know why they’re using objectForKey: instead of boolForKey:. If you have any insight into that, I’d love to hear it.

And that’s how to sniff out secret, undocumented preferences on OS X. It’s mostly not necessary, of course, since the interesting ones will show up on the Internet anyway. If nothing else, I hope you’ve enjoyed this look at the power of Objective-C.

I’ve uploaded a sample project you can play with. Unlike just about every other Cocoa project out there, all of the interesting code is in main.m. I learned about Objective-C method swizzling from Scott Stevenson and Mike Ash. You can probably learn a lot from them too.

blog comments powered by Disqus