Building a Better Objective C

If you’ve been following along with my recent posts regarding enums and Objective C, you’ll notice a theme: Objective C is quite capable of expressing what we want, it’s just extremely verbose, especially compared to the improved syntax that Swift brings to the table.

There are many significant drawbacks to Swift, especially when you need to interop with ObjC code. In this world, we need something better. We need to be able to transparently bridge between the world of modern syntax and conveniences without being shackled to the days of C programming, while at the same time, being able to maintain 100% interop with all of our existing ObjC code.

We could continue to suffer through the ObjC syntax, or, we can do something about it.

Framing the Solution One solution to solving the problems I exposed in the series with enums is to use a bunch of MACROs. That works, but it doesn’t get us nearly where we want to be: a modern, sleek syntax that is both easy to write, and more importantly, easy to read and reason about.

This is where we want to be:

enum CompassPoint: North | South | East | West

That’s it. That’s all it should take to define one of the “traditional” enums.

enum Barcode:
  | UPCA(numberSystem :: int, manufacturer :: int, product :: int, checkInt :: int)
  | QRCode(code :: NSString)

And above is what the “associative value” enum looks like. I’d like to call out something here, we actually have more information encoded in the enum then Swift here – we capture the name of the components1. This adds much more clarity to what the values actually are.

And finally, the raw values:

enum ASCIIControlCharacter :: NSString:
  | Tab := "\t"
  | LineFeed := "\n"
  | CarriageReturn := "\r"

You might be wondering at how we are going to accomplish this. Well, it’s actually pretty straight forward. Here are the items we’ll be building:

  1. A parser for our enum syntax
  2. A tool to convert our parse tree to ObjC code

That’s it. We don’t actually need anything else2. I’ll show how we can leverage Xcode’s built-in extensibility points to give us nice error reporting and seamless integration of our new enum definition.

I will be calling this new language: Proteus.

Building the Parser There are basically two ways to generate the parser: handwrite one or use some tools to generate one from a grammar. I’m going to handwrite our parser, primarily because our enum grammar is quite simple and it’s really easy to provide very friendly error messages with a handwritten parser.

Note: I’ll only be showing building the most simple of parsers accepting only the basic version of the enum. The full github link will be posted at the end. Overtime I’ll continue to add support more features.

The work is going to be broken up into the following components:

  1. Scanner – this is going to break up the input file into a set of tokens.
  2. Analyzer – this is going to take the tokens from the Scanner and apply meaning to them, returning an array of Constructs.
  3. Construct – a representation of the different type of language constructs, such as enum or func.
  4. Rewriter – a function that can take a Construct and turn it into the appropriate Objective C header and implementation files.

I will be writing the components in Swift – I like the irony of building a better Objective C in a language that I think didn’t live up to that promise. =)

The full source for the project can be found here: https://github.com/owensd/proteus3.

If you take a look at the code for the scanner (in lexer.swift), the output for the CompassPoint declaration are the following tokens:

  1. Token(Keyword) “enum”
  2. Token(Identifier) “CompassPoint”
  3. Token(Colon) “:”
  4. Token(Identifier) “North”
  5. Token(Pipe) “|”
  6. Token(Identifier) “South”
  7. Token(Pipe) “|”
  8. Token(Identifier) “East”
  9. Token(Pipe) “|”
  10. Token(Identifier) “West”

This input is passed into the Analyzer which outputs an Enum construct.

Enum:
  typeName = "enum"
  identifier = "CompassPoint"
  options = [
    "North",
    "South",
    "East",
    "West"
  ]

Then there is the rewriteEnumToObjC rewriter function. The full code for that is below:

private func rewriteEnumToObjC(value: Enum) -> (header: String, implementation: String)
{
    var header = "@interface \(value.identifier) : NSObject\n\n"

    var implementation = "#include \"\(value.identifier).h\"\n\n"
    implementation += "#define RETURN_ENUM_INSTANCE() \\\n"
    implementation += "    static \(value.identifier) *instance = nil;\\\n"
    implementation += "    static dispatch_once_t onceToken;\\\n"
    implementation += "    dispatch_once(&onceToken, ^{\\\n"
    implementation += "        instance = [[\(value.identifier) alloc] init];\\\n"
    implementation += "    });\\\n"
    implementation += "    return instance;\n\n"

    implementation += "@implementation \(value.identifier)\n\n"

    for option in value.options {
        header += "+ (\(value.identifier) *)\(option.name);\n"
        implementation += "+ (\(value.identifier) *)\(option.name) { RETURN_ENUM_INSTANCE(); }\n"
    }

    header += "\n+ (NSArray *)values;\n"
    implementation += "\n+ (NSArray *)values\n"
    implementation += "{\n"
    implementation += "    static NSArray *values = nil;\n"
    implementation += "    static dispatch_once_t onceToken;\n"
    implementation += "    dispatch_once(&onceToken, ^{\n"
    implementation += "        values = @[ "

    for (idx, option) in enumerate(value.options) {
        implementation += "\(value.identifier).\(option.name)"
        if (idx != value.options.count - 1) {
            implementation += ", "
        }
    }

    implementation += " ];\n"
    implementation += "    });\n\n"
    implementation += "    return values;\n"
    implementation += "}\n"

    header += "\n@end\n"
    implementation += "\n@end\n"

    return (header, implementation)
}

swift

The basic concept is to literally just write the header and implementation files as you would normally.

With each of those components in place, it’s time to hook it up!

Integration with Xcode If you download the project I linked, there will be a command-line tool called protc. It takes two parameters:

  1. -file – the path to the .prot file that contains our enum definition
  2. -output – the path that the .h and .m files will be written to

So here’s the magic… Xcode has these things called “Build Rules”. It’s basically how anything gets compiled within Xcode. Well, we can create our own build rules for our .prot files.

Step 1: Build Rules To do that:

  1. Select your project in the project navigator
  2. Select the target for your app/tool
  3. Select the “Build Rules” tab in the project editor
  4. Add a new build rule

The script will be filled in with this content:

rm "${DERIVED_FILE_DIR}/${INPUT_FILE_BASE}.h" 2> /dev/null
rm "${DERIVED_FILE_DIR}/${INPUT_FILE_BASE}.m" 2> /dev/null
/Users/owensd/Library/Developer/Xcode/DerivedData/protc-ecehehjngfljckcirxwdltnqqayl/Build/Products/Debug/protc -file ${INPUT_FILE_PATH} -output ${DERIVED_FILE_DIR}

Then be sure to set the “Output Files” to:

  1. $(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).h
  2. $(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).m

Here’s a screenshot of what it looks like:

Custom build rules allow us great flexibility.

: .caption

Step 2: Create your .prot file Next, create your .prot file just as you would any file. The trick is to add it to your “Compile Sources”.

  1. Select your project in the project navigator
  2. Select the target for your app/tool
  3. Select the “Build Phases” tab in the project editor
  4. Add your .prot file to the list of “Compile Sources”

Don’t forget this step or nothing will seem to be working!

: .caption

Step 3: Use your new enum! That’s really it. Now when you build your project, the .prot will be processed and in turn the generated .m file will be compiled into your project’s target.

To use it, simply add the header file as normal:

#import <Foundation/Foundation.h>
#import "CompassPoint.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (CompassPoint *point in CompassPoint.values) {
            if (point == CompassPoint.North) {
                NSLog(@"direction is north");
            }
            else if (point == CompassPoint.South) {
                NSLog(@"direction is south");
            }
            else if (point == CompassPoint.East) {
                NSLog(@"direction is east");
            }
            else if (point == CompassPoint.West) {
                NSLog(@"direction is west");
            }
        }
    }

    return 0;
}

objc

If you set your “Output Files” to be ${DERIVED_FILE_DIR}, the header paths have no trouble finding the generated header.

Step 4: Handling Build Errors Of course, sometimes you might make a mistake in your .prot file. It would suck if there was no way to handle this or to be notified of this… but of course that’s not the case!

Change your .prot file to this:

enum CompassPoint North | South | East | West

Notice the missing : between CompassPoint and North. Re-build your project, and voila!

Errors are reported and clickable!

: .caption

Not only can the error be reported, but it will cause your project to report a build failure. Those errors are also linked back to your source, so double-clicking will open your .prot file and show the error there.

Pretty cool.

The only missing thing is code completion and colorization. Unfortunately, to the best of my knowledge, those two features require full-blown Xcode plug-ins – way out of scope for this blog entry.

Next Steps Over the years, Objective C has attempted to get facelifts. Way back the 1997 timeframe, there was a project to create a “modern syntax” for ObjC. It didn’t go well. Then there was the ObjC-Java bridge that came in 2001. That also didn’t go well.

However, in 2006 we got ObjC 2.0. That was pretty good step forward for the Objective C language. And of course, in 2014, we got Swift.

I think the biggest disservice we can do to the Cocoa developer community is remove the underpinnings of the ObjC runtime. It is the language’s, and I truly believe, the platforms’ greatest strength.

I believe if we hide the complexities of C from our source code and focus on letting the power of the ObjC runtime shine through in our code, we can create a new language that provides of the great flexibility of the ObjC runtime while still accomplishing many of the goals that Swift is attempting to solve – namely safer code by default.

So I guess this is the start of the project I’m calling Proteus. We’ll see how far it gets.

  1. Note that you can put labels here in Swift as well, they just are not required.
  2. Now, we could go the extra mile on step #2 and simply emit the LLVM code, but honestly, I don’t have time to go down that route. It’s also not necessary for what I want to accomplish here.
  3. Here’s the repo link for the state at the time of authoring this blog entry: https://github.com/owensd/proteus/tree/78d0b2ad729948fcef7e6da80e966574b372cc91.
Building a Better Objective C