Kotan Code 枯淡コード

In search of simple, elegant code

Menu Close

Using Google Protocol Buffers in Objective-C on iOS and the Mac

For all the hype and buzz surrounding web services and SOAP and JSON these days, sometimes we still need to deal with sending and receiving binary messages. In fact, we need to do this far more often than you might think. When we think about sending and receiving messages via TCP socket streams or storing information in binary format, one option that has become increasingly popular lately is Google’s Protocol Buffers, Google’s standard data interchange format.

Firstly, why do we need to use a different interchange format? With Cocoa, we have serialization with the NSCoding protocol. Well, the NSCoding protocol only really works best when both sides of the data interchange are Cocoa applications. If you want to exchange messages from C# to Java or from iOS to C++ and so on, and you want to describe this interchange pattern in a platform agnostic IDL, then this is where protobufs really start to shine and you can see their real value.

Feel free to go read the documentation on protobufs, but the cliff notes are as follows: By creating .proto IDL files (simple text describing message schemas) you can use the protoc compiler to generate code in C++, Java, or Python. The generated code contains simple classes representing the messages described in your IDL, as well as methods that allow you to dump the messages to a string and read and write the messages from various formats, including raw binary.

In terms of utilizing protobufs in iOS/Mac OS X there are a couple of options. There is an open-source extension to the protobuf compiler that produces Objective-C code, but it’s newest release is three years old and I couldn’t get it to compile. I would much rather use the protobuf trunk so my code is up to date. So the rest of this blog post is devoted to showing you how to use the C++ generated protobuf classes in either your iOS or your Mac application code without any hackery, shennanigans, or reliance on stale OSS projects.

First, you’ll need to download the source code for protoc, the protocol buffer compiler. You can get this from the link earlier in the blog. Follow the instructions on your Mac to autogen, configure, and make the compiler (you’ll need the command-line developer tools turned on, not just the stock Xcode install). Once you’ve got the compiler on your machine, you can create a sample message.

Here’s some IDL for a message that will send indications of zombie sightings during the apocalypse:

package kotancode;

enum ZombieType {
    SLOW = 0;
    FAST = 1;
}

message ZombieSighting {
    required string name = 1;
    required double longitude = 2;
    required double latitude = 3;
    optional string description = 4;
    required ZombieType zombieType = 5 [default = SLOW];
}

Now that you have a protobuf IDL file, you can run the protoc compiler (see protoc –help for syntax) to generate the C++ classes. In this case, it produces a zombie.pb.h and a zombie.pb.cc (I renamed it to zombie.pb.cpp since I’m more familiar with that extension).

Next there’s a bit of manual mucking around but you only have to do it once. In the source code directory you downloaded from Google, delete all of the files related to unit testing, delete the compiler source, and I actually got rid of the gzip sources as well because I had linker issues with those. Add this google directory (e.g. ~/Downloads/protobuf-2.4.1/src/google) to your Xcode project (iOS or Mac, doesn’t matter.. isn’t cross-platform awesome?). You’ll also need to modify config.h so that it doesn’t include the “tr1” namespace prefix. You may not need to do this, but on my Mac I had to. For example, you’ll need to change the HASH_NAMESPACE #define to just std instead of std::tr1 and you’ll need to change the HASH_MAP_CLASS from tr1::unordered_map to just unordered_map. Again, this is a pain but you only have to do this once and then never again for all of your subsequent protobuf projects.

Next, add the config.h file to your Xcode project (this is in the root of the protobuf source dir). Now add the source root (one level above the google directory) to your Xcode project’s header search paths (e.g. mine was ~/Downloads/protobuf-2.4.1/src). You do not need to statically link the protobuf library because you have all of the source code directly in your project, which comes in really handy when you get EXC_BAD_ACCESS errors in your messaging layer.

Now we can get down to business. You have a couple options in terms of how far you allow the C++ stuff to leak into your application. I prefer to write a single wrapper object around the C++ stuff so that the rest of my application can be written in Objective-C and I can minimize the syntax blending. While this blending makes cross-platform stuff on the Mac easy, it can also get confusing when you start bleeding C++ memory management into your UIKit view controllers.

So, here’s the .h and .mm (note the extension!) of my ZombieSightingMessage Objective-C class. Right now it just has a method called doSomething that lets me verify that I can construct, serialize, dump, and de-serialize protobuf messages but you should be able to extrapolate from this how you might wrap your messaging layer behind a small number of Objective-C++ classes.

// -- ZombieSightingMessage.h - note my C++ object is not in the public interface.
#import <Foundation/Foundation.h>

@interface ZombieSightingMessage : NSObject
- (void)doSomething;
@end

// -- ZombieSightingMessage.mm
#import <UIKit/UIKit.h>
#import "ZombieSightingMessage.h"
#import "zombie.pb.h"

@implementation ZombieSightingMessage

- (void)doSomething {
    // Doing random stuff with a UIView here to show the mixing
    // of C++ and Objective-C/Cocoa syntax in the same file...
    UIView *uiView = [[UIView alloc] init];
    [uiView setCenter:CGPointMake(20, 10)];

    // instantiate my protobuf-generated C++ class.
    kotancode::ZombieSighting *zombieSighting = new kotancode::ZombieSighting();
    zombieSighting->set_name("Kevin");
    zombieSighting->set_description("This is a zombie");
    zombieSighting->set_latitude(41.007);
    zombieSighting->set_longitude(21.007);
    zombieSighting->set_zombietype(kotancode::ZombieType::FAST);

    // Some small tomfoolery required to go from C++ std::string to NSString.
    std::string x = zombieSighting->DebugString();
    NSString *output = [NSString stringWithCString:x.c_str() encoding:[NSString defaultCStringEncoding]];
    NSLog(@"zombie: %@", output);

    // Instantiate another zombie from the previous zombie's raw bytes.
    NSData *rawZombie = [self getDataForZombie:zombieSighting];
    kotancode::ZombieSighting *otherZombie = [self getZombieFromData:rawZombie];

    // Dump the second zombie so we can see they match identically...
    NSString *newOutput = [NSString stringWithCString:otherZombie->DebugString().c_str() encoding:[NSString defaultCStringEncoding]];
    NSLog(@"other zombie: %@", newOutput);

    // Grimace all you want, but this is C++ and we need to clean up after ourselves.
    free(zombieSighting);
    free(otherZombie);

}

// Serialize to NSData. Note this is convenient because
// we can write NSData to things like sockets...
- (NSData *)getDataForZombie:(kotancode::ZombieSighting *)zombie {
    std::string ps = zombie->SerializeAsString();
    return [NSData dataWithBytes:ps.c_str() length:ps.size()];
}

// De-serialize a zombie from an NSData object.
- (kotancode::ZombieSighting *)getZombieFromData:(NSData *)data {
    int len = [data length];
    char raw[len];
    kotancode::ZombieSighting *zombie = new kotancode::ZombieSighting;
    [data getBytes:raw length:len];
    zombie->ParseFromArray(raw, len);
    return zombie;
}

@end

Now when I run this application, I get no linker errors, my stuff compiles, and my console output log shows the following:

2012-10-14 16:42:23.919 ProtoPhone[15434:c07] zombie: name: "Kevin"
longitude: 21.007
latitude: 41.007
description: "This is a zombie"
zombieType: FAST
2012-10-14 16:42:23.920 ProtoPhone[15434:c07] other zombie: name: "Kevin"
longitude: 21.007
latitude: 41.007
description: "This is a zombie"
zombieType: FAST

While there’s an awful lot more to protocol buffers than just this, hopefully this blog post has shown you a way to get protobufs up and running in your iOS and Mac applications without you having to depend on a semi-maintained modification of protoc and can instead use the most currently available code straight from Google.

Because this is a binary, cross-platform data interchange format, you can use these messages to talk to web services, to communicate with C++ back-ends running on Unix/Linux boxes in the enterprise, or even use these for efficient persistence and file storage, which also comes in handy on storage-limited devices like iPhones and iPads.