on
Generating Swift Documentation From Objective-C
I’ve been working on building documentation for Quiet Modem Project and I recently came up against a snag in documenting my iOS library that contains Objective-C. Although it’s mostly advisable to stick to writing iOS libraries in Swift these days, I chose Objective-C because it felt nicer when wrapping C libraries. Since the core part of my project is a C library, being able to wrap it cleanly makes life a little easier for me.
The standard I’ve set for my documentation is that I want to show both the code that declares the Class or Function (or some close simplification of it) as well as structured commentary written in plain English. If a function takes two parameters, I want to show the function’s header and a good thorough explanation of what it expects for parameters, what it returns, and any extra notes about how it behaves.
It’s easy enough to programmatically generate API documentation for Objective-C. The wonderful Doxygen is quite capable of parsing structured documentation out of comments. I’m not a fan of the actual pages and stylesheets it generates, but the XML output contains all the relevant documentation, and this output can be used to feed a separate documentation frontend like MkDocs. The structured output contains something like a structured parsing of the code itself as well as the comment strings I put alongside it. So is this the end of the story?
This method can generate good documentation for Objective-C, but iOS developers are going to want Swift documentation. This means that what we really want to do is generate both the Objective-C docs and the corresponding docs for the translated Swift code. There’s good precedent for this, of course – Apple’s own documentation mostly does this. How would we translate Objective-C documentation into Swift documentation? There are major semantic and syntactic differences between the two languages.
I thought about this for a while and started to feel despair. My project has bindings in JavaScript, Java (Android), Obj-C/Swift and then the original itself in C. One change in the core C library can require updates in documentation for 5 languages. Any solution I came up with would have to be automated, but there was surely no mode for Doxygen to generate Swift documentation from Objective-C.
I had hoped to find that someone else had run into this problem before and had solved it. It was at this point I discovered the jazzy tool. Finding this gave me a lot of optimism that I was going to be able to automate the translation process. Although I couldn’t find a way to get jazzy to do the translation for me, I realized that it relied on a tool called SourceKit that’s supplied by Xcode and that can do some kinds of source code interactions, which I thought might include Objective-C to Swift translations. This made sense to me as I knew that Xcode must somehow have the ability to figure out a Swift header from an Objective-C .h file.
I got SourceKitten running and started poking around SourceKit’s API. With a bit of Google searching, I found a request that would generate a Swift file from an Objective-C file. I was glad someone else had documented this since I would have never figured it out by myself. Yes, you really do need that UUID, which defines the SourceKit function you’re calling. It’s not localized or specific to your project.
Running sourcekitten request --yaml header.yaml | jq -r '.["key.sourcetext"]' > Foo.swift
transforms Foo.h into Foo.swift.
This felt like a bit of magic the first time I saw it. The transformation isn’t always perfect, but it does a pretty good job considering it’s automated. Even our structured comments moved over.
Code translation was an important step forward for what I wanted to do. This still wasn’t enough to build the documentation though, as what I need is the structured code listings and comments that Doxygen and similar tools build. If Doxygen had a Swift mode, this would have been the end of the story. I would just take these translated Swift files and send them through Doxygen. Unfortunately this isn’t the case.
I did know that SourceKit must have something like this capability since tools like jazzy were using it. Generating documentation is thankfully a much more straightforward use of sourcekitten. sourcekitten structure --file Foo.swift
gets us the structured output in a format that’s not so different from Doxygen’s XML output. With some work this can be translated into a nice Markdown file that MkDocs will consume.
Extracting the structured comments is trickier. In Objective-C files, SourceKit actually has some support for pulling structured comments that are in a Javadoc-like style, which is compatible with my Doxygen-style comments. It doesn’t offer the same support for Javadoc-style comments in Swift though as these comments are no longer considered relevant. Instead it is expected that Swift comments are written in Markdown. Even though SourceKit was able to maintain our comments, it can’t actually consume them in a useful manner.
The best option I’ve found is using sourcekitten doc --single-file Foo.swift
to get the block of comments that is associated with each declaration. I believe the easiest option to turn these comment blocks into structured documentation is writing a parser specifically for this task. Thankfully this is considerably easier than extracting the structured code information. This allows me to get the plain English explanation of each function parameter displayed properly alongside the function declaration.
With all of these tools in hand, it is finally possible to generate proper structured documentation for Swift users from Objective-C code. Although I’m sure there are many who would tell me to just give up Objective-C and wrap my libraries in Swift, I’m happy to say that a path to automated documentation does exist for stubborn programmers like myself.