The Incredible Shrinking Docs

Wouldn't you know, I was wondering about documentation in Xcode 8, and indeed it has fundamentally changed. The DevPubs team has hugely reduced the size of the docs and apparently integrated them with Xcode so that there is no longer a need for a separate download step.

Running the Xcode 8 beta, I find I'm able to browse the docs with WiFi turned off. I know I'm looking at the Sierra docs, because NSGridView is there. Furthermore, docsets are no longer listed in the Downloads pane of the prefs panel. It's like magic — kudos to the DevPubs team!

I'm guessing this news about the docs was announced during the Platform State of the Union. I missed that session when it was live-streamed, so I'll have to watch it later. I'll see if it confirms my understanding, and I'll think about implications for AppKiDo.

The Developer Tools people at Apple have a lot of momentum lately. I can't remember when it felt so much like they were working hard to connect with us, to show that they hear us (the culture around Swift seems incredibly positive), to create cool tools, and to take away pain points.

I wonder if squashing the documentation size was important not only for Xcode but for the Swift Playgrounds iPad app and/or future possibilities for using iPads for development.

The New Adventures of Old AppKiDo

I have been making progress on a reboot of AppKiDo, which is a Mac app that I wrote for browsing the Cocoa documentation that comes with Xcode. AppKiDo stopped working in 2014, during the betas of Xcode 6, because of changes in the structure of Apple's docsets.

I now have it up and running again, in a preliminary but usable form. (The code is in a private repository; I will push it to the public one after ironing some things out. [UPDATE: Eh, why wait? The code is public at https://github.com/aglee/appkido. Remember, it's very much a work in progress.])

The best news, aside from having it run at all, is that it's launching much more quickly than it used to. There's still a lag, but it's much shorter now, because instead of laboriously parsing all the HTML files, I get almost everything I need from the Core Data store inside the docset.

There are serious limitations in the current version that will take time to address:

  • Still Objective-C API only. For example, if you search for "string" the search results won't include the Swift String class.
  • Xcode must be at /Applications/Xcode.app.
  • You have to pick the docset you want to browse at launch time.
  • Window states aren't saved between launches.
  • The "ALL" options are missing (as in "ALL Class Methods", "ALL Instance Methods", etc.).
  • And more.

As I write this, the keynote for WWDC 2016 is hours away. For the first time in a long while I am experiencing an old familiar feeling of suspense. Will there be some change to the docs in Xcode 8 that will upend everything I've been doing?

OMQuickHelp plug-in

OMQuickHelp is really cool:

This plugin allows you to use Dash instead of Xcode's own documentation viewer when using option-click (or the equivalent keyboard shortcut) to view the documentation for the selected symbol.

This is much more convenient than the "method-aware" service I provide in AppKiDo. You don't have to select any text — just Option-click anywhere on the symbol you want to look up. To install the plug-in, just build the project and restart Xcode.

If you want the search to be done in AppKiDo instead of Dash, find these lines in OMQuickHelpPlugin.m:

BOOL opened = [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"dash://%@", searchString]]];
if (!opened) {
    return NO;
}

Replace the lines above with these lines:

NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
 
[pboard declareTypes:@[NSPasteboardTypeString] owner:nil];
[pboard setString:searchString forType:NSPasteboardTypeString];
 
if (!NSPerformService(@"Look Up in AppKiDo", pboard)) {
    return NO;
}

Again, build the plug-in and restart Xcode. Replace "AppKiDo" with "AppKiDo-for-iPhone" if that's your pleasure.

Note that searching with Dash or AppKiDo is not exactly like Xcode's default Option-click behavior. Normally, Xcode shows you the documentation for whatever the compiler thinks the symbol you selected is. This is unambiguous; there is at most one relevant documentation entry. For example, if you Option-click "view" in the following code, you'll see the docs for NSViewController's view method.

// Yes, this is absurd. It's just for purposes of discussion.
[[NSViewController new] view];

Dash and AppKiDo are different in that they do purely substring-based searches, using no semantic information. Searching for "view" in either app will return a whole bunch of results containing "view". You will have to pick through the search results to find the one you want.

It may be worth occasionally picking through multiple search results if this means you can use your preferred search tool. It may even be useful to see the other search results, which may include related symbols you weren't aware of. And you can always use Xcode's Quick Help inspector (Option-Command-2) to see the same documentation that Option-click would normally have shown before you installed OMQuickHelp.

[Edited.]

Method-aware doc searches

I finally got around to updating AppKiDo so that the various URLs it uses point to appkido.com rather than the now-defunct MobileMe site. The new version requires 10.5; I got tired of not using fast enumeration.

While I was at it I added the system service I mentioned I was thinking about, and I used NSServiceCategory to put it in the "Searching" category of the Services menu. I considered putting it in the "Development" category, but I think what AppKiDo does is more akin to Dictionary than to Instruments.

Services appkido

I made the search service "method-aware", which means it tries to detect method names in your text selection. I'm hoping other doc-search apps will add this feature. They can use my simple method-parsing class, if it helps. For people who have use for the method extraction but don't want to launch AppKiDo, it might be nice to put it into a standalone Service, but I don't know if I'll get to that.

The rest of this post will be an explanation of the "method-aware" feature, copied from the release notes.


"Look Up in AppKiDo" is a service that you can invoke from any application where you have selected some text. It activates AppKiDo and performs a search for that text.

Often what we want to search for is a method name. AppKiDo tries to help by determining whether the selected text contains an Objective-C message-send or method declaration. If so, it searches for that method name. Otherwise, it searches for the literal text you have selected.

For example, in Xcode if you have [self flyToX:100 y:200 z:300], you can double-click one of the square brackets to select the whole expression, then invoke this service. AppKiDo will search for the method name flyToX:y:z:.

If you happen to be in BBEdit, where double-clicking a bracket selects the text inside the brackets, the service should still work. If there is leading whitespace or a cast, or newlines or comments anywhere, it should still work, so if you have lines like this you can select them all and then invoke the service:

(void)[self flyToX:100  // cast to void to discard the return value
                 y:200
                 z:900 /*300*/];

Note that "Look Up in AppKiDo" doesn't work if there is an assignment in the selected text. For example, it won't work if you select this whole line:

BOOL didFly = [self flyToX:100 y:200 z:300];

The workaround is to select just the message-send — the part after the "=".

Another intended use is when you're looking at code that declares a method and you want to search for that method name. For example, you can select these lines and it will search for browser:child:ofItem: (the "-(id)" will be ignored):

- (id)browser:(NSBrowser *)browser
        child:(NSInteger)index
       ofItem:(id)item

This service assumes well-formed Objective-C. You might get unexpected results otherwise. If there are nested messages, it uses the top-level one. The algorithm mainly looks at punctuation — delimiters like brackets and a few other characters that need special treatment. The basic idea is that it ignores anything between delimiters, like (blah blah blah), [blah blah blah], or {blah blah blah}. For this reason it should work if your selected code contains blocks or the new object literals.

If you use the "Look Up" service, remember to assign a hotkey in System Preferences > Keyboard > Keyboard Shortcuts > Services for maximum convenience.

Docsets can have bugs

If you've grabbed the latest AppKiDo (0.988; release notes here) and it seems to be a bit slower starting up, here's why.

AppKiDo is a documentation browser for the Cocoa APIs. As such, it needs information about all sorts of API symbols: class names, protocol names, method names, function names, typedefs, and constants. This information is scraped from two places: Objective-C header files and special bundles called docsets.

A docset contains a bunch of HTML files (the documentation) along with a "docset index" — a SQLite database file named docSet.dsidx. One thing the docset index does is it maps each API symbol to two file paths:

  • The header file where the symbol is declared.
  • The HTML file where the symbol is documented.

The way AppKiDo is designed, it needs to know what framework every symbol belongs to. NSString and its methods belong to the Foundation framework, NSView and its methods belong to the AppKit framework, and so on. I've been getting this framework information by querying two tables in the docset index:

  • ZHEADER, which contains paths to header files, along with the name of the framework each header file belongs to. NSString.h belongs to the Foundation framework, NSView.h belongs to the AppKit framework, and so on.
  • ZTOKENMETAINFORMATION, which contains information about each API symbol, including a foreign key to ZHEADER.

(By the way, the docset index is actually a Core Data database. Normally one shouldn't access Core Data's underlying tables directly, but AppKiDo's access is read-only so there's no risk of corrupting the database, and SQL queries were simpler to implement than reverse-engineering the managed object model. Also, the docset index is relatively static, although that's a topic for another discussion.)

Now here's the thing. For some symbols, ZTOKENMETAINFORMATION is missing the foreign key to ZHEADER. This means you can't tell purely by querying the database which header file those symbols are declared in or which framework they belong to.

As far as I can tell, this is simply a bug in the docset. I suspect if I studied up on how docsets get created (which I should really do someday) I'd have some idea why the bug is there. In any case, it's there, and when I get a chance to write it up in more detail I'll submit a Radar.

If you have Xcode 4, and if Apple hasn't fixed the docset by the time you read this, you can see an example of this. In an iOS project, type "NSFetchedResultsControllerDelegate" and Option-click on it. You'll see that the Quick Help popup is missing the "Declared In" line that appears for most other symbols.

Missing the

Compare this to the Quick Help popup for NSString:

Showing the

Because AppKiDo (or, for iOS docs, AppKiDo-for-iPhone) can't tell what framework the affected symbols belong to, it never loads the documentation for those symbols.

My workaround was to add a second query that assumes the path to a symbol's HTML file contains the name of its framework. This is not always true, but it's true often enough for AppKiDo to figure out that NSFetchedResultsControllerDelegate belongs to Core Data.

Doing the second query means AppKiDo takes a bit longer to start up, which you may or may not be able to tell given how long startup is already. You probably wouldn't notice except that the query contains a LIKE clause, which makes it relatively slow.

For more gory details, you can see the relevant commit on GitHub.

So now you know.