How To Create an Xcode Plugin: Part 3/3

Wrap up your Rayrolling Xcode plugin by getting your hands dirty with more assembly language and Cycript in this final instalment of the three-part tutorial series. By Derek Selander.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Implementing the Helpful Payroll

Create a new NSObject Category and name it Rayrolling IDEDocWebViewContentViewController. By now, this process shouldn’t be surprising.

Open NSObject+Rayrolling_IDEDocWebViewContentViewController.m and replace the file’s contents with the following:

#import "NSObject+Rayrolling_IDEDocWebViewContentViewController.h"
#import "NSObject+MethodSwizzler.h"
#import "Rayrolling.h"
#import <WebKit/WebKit.h>

@implementation NSObject (Rayrolling_IDEDocWebViewContentViewController)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setup) name:NSApplicationDidFinishLaunchingNotification object:nil];
    });
}

+ (void)setup {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [NSClassFromString(@"IDEDocWebViewContentViewController") swizzleWithOriginalSelector:@selector(webView:decidePolicyForNavigationAction:request:frame:decisionListener:) swizzledSelector:@selector(Rayrolling_webView:decidePolicyForNavigationAction:request:frame:decisionListener:) isClassMethod:NO];
}

- (void)Rayrolling_webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
                   request:(NSURLRequest *)request
                     frame:(WebFrame *)frame
          decisionListener:(id<WebPolicyDecisionListener>)listener
{
    if ([Rayrolling isEnabled]) {
        [listener use];
    } else {
        [self Rayrolling_webView:webView decidePolicyForNavigationAction:actionInformation request:request frame:frame decisionListener:listener];
    }
}

@end

Note that this version of load doesn’t swizzle as usual, but instead listens for NSApplicationDidFinishLaunchingNotification. Xcode 6.3 loads plugins during applicationWillFinishLaunching:, which means that IDEDocWebViewContentViewController is not loaded by the time this code is executed.

IDEDocWebViewContentViewController is unique in that you are modifying a component from a plugin as illustrated by the location of where the binary resides on your computer (/Applications/Xcode.app/Contents/PlugIns/IDEDocViewer.ideplugin/Contents/MacOS/IDEDocViewer:). This is different compared to other private classes you augmented as they are loaded in as frameworks.

Typically plugins are used to modify or complement existing code so it makes sense for them to be loaded at a later time. As a result, you are listening for when the application did become active and then modifying the class after it has loaded.

Now run this program to create a fresh plugin, then quit and relaunch Xcode. Navigate to the documentation and look up some class documentation; you’ll be greeted by a chorus of beautiful voices singing about WWDC.

Achievement unlocked: Rayrolling like a boss! :]

Where to Go From Here?

You can download the final project from this tutorial series here.

You’ve completed your Rayrolling plugin! On the journey, you’ve learned some pretty cool things about LLDB tricks, scripts, Dtrace, Cycript, and assembly.

But the journey isn’t over. The Xcode Plugin community is a very welcoming one; feel free to create a new plugin and add it to Alcatraz. Someone else out there will definitely appreciate your work, even if it’s the smallest of modifications.

The workflow and tricks you’ve learned in this series will be completely different compared to the workflow of someone else who is attempting to solve the same problem. So go forth, explore and have fun!

Do you have any cool tips or tricks for exploring Xcode, or do you have any questions or comments on this tutorial series? Feel free to join the discussion below!