Infrastructure

Intro

I’ve found it difficult the past few years to be productive working on my own app, Aether. Part of the trouble has been that it was always difficult to release a new version. I had to manually build it, write release notes, update and reindex the built in help, create a new DMG, create a ZIP file (for Sparkle), edit the Sparkle appcast XML, and FTP everything to my server. This was a completely manual process, and each step takes a minute or two at the very least. This is just for the non-app-store version, too! It all added up to enough friction that I tended to avoid releasing updates.

Also, often when I’d sit down to work on development, I’d end up spending a significant chunk of time working through a backlog of customer support email. Customer support is very important, but it’s also really time consuming. I’d often find myself answering variations on the same questions over and over. I saved canned responses for a few common issues, but I’d still often spend 10-15 minutes answering a single email.

None of this is the thing I actually love to do: write code! In order to address these problems and give me more time to actually work on developing Aether, I’ve decided to make some infrastructure and process improvements. These have made a HUGE difference, and I’m finding it much more fun to work on the app itself again. I thought I’d outline the solutions that have worked for me, in the hopes that others may find them useful.

Build Server

I started by thinking it would be fun to write a web app to host and manage builds of Aether. Maybe I’d even use Swift on the server! Of course, that would be a huge undertaking, and I was lucky to find that a few people had already written just such a thing. I ended up using Appcaster (with some changes in my own fork), a Node.js app specifically built to host and manage Mac apps that use Sparkle for updates.

Appcaster makes it really easy to upload new builds, automatically generates and serves Sparkle appcasts, lets me write release notes in Markdown, and even allows for multiple deployment channels (e.g. Developer, Beta, Release). It also provides URLs that always point to the download and release notes for the latest version of Aether (DMG and ZIP) on each channel. Links on the Aether website point to these so that uploading a new build no longer requires any manual changes to the website at all.

Uploading New Builds

With Appcaster up and running (I’m using Heroku to host it), I wanted a way to quickly deploy new builds to the server. I wrote a quick-and-dirty Bash script to be run as an Xcode build phase. I put it in a custom target that first builds the app itself, then runs the script. The script takes the built app, creates both DMG and ZIP files for it, uploads them (along with the dSYM) to Amazon S3, then adds a new build to Appcaster. With this script, uploading a new build is as easy as selecting “Upload Aether Build” in Xcode’s scheme selector, then hitting command-B. Builds are deployed only to the Development channel by default, but can easily be promoted to Beta and/or Release. You can find the current (as of this writing) version of the script here. Disclaimer: It’s not polished, and could definitely use some improvement. I’m also far from a Bash expert.

Reducing the Customer Support Burden

A very significant portion of the customer support emails I receive are questions that could be answered by better documentation. I’ve been bad about writing good built in help due to the difficulty involved. Up until now, adding a new help page has meant writing HTML by hand, adding links to it to other pages, updating the help index, then releasing an update to Aether. It is also difficult to point people to specific help pages, even when I do have a page that would help with support.

help.aetherlog.com

The obvious solution is to put help for Aether on the web. I’m not a web developer by any means, so I wanted something that would be easy for me to set up, work well, but most importantly be easy to maintain and improve. I settled on MkDocs, a static site generator specifically designed for doing project documentation. The source for an MkDocs site is just plaintext Markdown files, along with a single YAML configuration file. It’s very easy for me to write with a text editor. Adding a new page is as simple as creating a new Markdown file and adding it to the config.yml file. The site can be rebuilt and deployed to my web host by running a single, simple Bash script. It’s not much more difficult than writing a response email. Now, when I get an email asking a question I know will come up again, I can write a help page and link to it so that my answer is valuable to more than just one person.

I decided to open source the new Aether help site. You can find it on GitHub here.

Built-in Help

Of course, a website for help is great, and really helps me with email customer support. However, I also want users to continue to be able to find and access help from within Aether. Hooking up the Aether Help menu item to open help.aetherlog.com was of course trivial. More complicated is redoing the Help menu search field that is standard in Mac apps so that it searches the website.

It turns out there’s a protocol called NSUserInterfaceItemSearching that allows you to provide custom results when the user searches using the Help menu search field. MkDocs generates a JSON file index of the entire site by default. It was pretty simple todownload and parse that to provide an index for searching. To actually do the searching, I’m using BRFullTextSearch, an Objective-C full text search engine API and wrapper for CLucene. I’m usually very hesitant to include third party libraries, but in this case it solved a problem I wasn’t interested in solving myself.

Conclusion

Good infrastructure can remove many of the tedious tasks associated with releasing, maintaining, and supporting apps, allowing for more time to focus on the fun parts of development. Making these changes has helped me refocus on improving Aether. I hope the tools I’ve talked about here are helpful to others.

Objective-C Class Properties

While looking through the release notes for Foundation in iOS 10/macOS 10.12 Sierra, I noticed an item under “Overall API Updates”

Use of class properties, in both Swift as well as Objective-C, latter using the new “@property (class)” declaration.

Xcode 8 ships with a new versions of Clang and LLVM (800.0.2.24.1, and 8.0.0 as of the first beta). The new version of Clang adds support for class properties in Objective-C. The feature is not yet fully documented anywhere that I can find1, so I did a little experimenting.

Let’s say you have a Car class and want newly created cars to have an optional, configurable default color. In Swift you can already do:

class Car {
    static var defaultColor: UIColor?
}

Previously, in Objective-C, you might have done this using:

@interface Car : NSObject
+ (UIColor *)defaultColor;
+ (UIColor *)setDefaultColor:(UIColor *)defaultColor;
@end

This has long been a fairly common approach, mirroring as it does the convention for instance property accessor methods. It is used in a number of places in the system frameworks on OS X and iOS.

With the new support for Objective-C class properties, you can now do the following:

@interface Car : NSObject
@property (class) UIColor *defaultColor
@end

Here the class specifier in the list of attributes for the @property means this will be a class property rather than an instance property.

It is important to note that unlike regular instance properties, Objective-C class properties can not be synthesized using @synthesize, nor are they automatically synthesized by the compiler. You are responsible for implementing accessor methods for them yourself. (You can also use an @dynamic for them to tell the compiler that accessor methods will be provided at runtime.) If you don’t provide methods for them, you’ll get a compiler warning, and @synthesize produces a compiler error:

Compiler warning/error

The implementation of the methods will depend on exactly what the class property does, but one common approach is to back them with a static variable:

static UIColor *_carDefaultColor = nil;

@implementation Car

+ (UIColor *)defaultColor { return _carDefaultColor; }
+ (void)setDefaultColor:(UIColor *)defaultColor { _carDefaultColor = defaultColor; }

@end

It seems a fair bet that this feature was added for better bridging into Swift, where class properties have always existed. Before real Objective-C class properties, you would have had to call the accessor methods using method/function syntax:

Car.setDefaultColor(.white())

Now, when an Objective-C class with a class property is bridged into Swift, you can use regular property syntax to access the class property:

Car.defaultColor = .white()

Of course, this feature provides a nice benefit even for pure Objective-C code, where you can now use dot-notation to access class properties:

Car.defaultColor = [UIColor whiteColor];

Presumably, in cases where it makes sense, Apple will be using the new Objective-C class properties in system API, improving bridging into Swift. It’s not clear if they will go back and update existing API to use the new syntax. So far, only NSGridView and allowsAutomaticWindowTabbing in NSWindow (both new APIs in 10.12) are using the new syntax, but that may change.

It’s nice to continue to see updates being made to Objective-C. As with last year’s addition of lightweight generics, etc., even if they’re being done primarily for bridging with Swift, they provide some nice improvements for all Objective-C programmers.


  1. It was discussed on the LLVM mailing list and bug tracker during development, of course. ↩︎