Objective-C Generated Code Guide

Describes exactly what Objective-C code the protocol buffer compiler generates for any given protocol definition.

Any differences between proto2 and proto3 generated code are highlighted. You should read the proto2 language guide and/or proto3 language guide before reading this document.

Compiler invocation

The protocol buffer compiler produces Objective-C output when invoked with the --objc_out= command-line flag. The parameter to the --objc_out= option is the directory where you want the compiler to write your Objective-C output. The compiler creates a header file and an implementation file for each .proto file input. The names of the output files are computed by taking the name of the .proto file and making the following changes:

  • The file name is determined by converting the .proto file base name to camel case. For example, foo_bar.proto will become FooBar.
  • The extension (.proto) is replaced with either pbobjc.h or pbobjc.m for the header or implementation file, respectively.
  • The proto path (specified with the --proto_path= or -I command-line flag) is replaced with the output path (specified with the --objc_out= flag).

So, for example, if you invoke the compiler as follows:

protoc --proto_path=src --objc_out=build/gen src/foo.proto src/bar/baz.proto

The compiler will read the files src/foo.proto and src/bar/baz.proto and produce four output files: build/gen/Foo.pbobjc.h, build/gen/Foo.pbobjc.m, build/gen/bar/Baz.pbobjc.h, and build/gen/bar/Baz.pbobjc.m. The compiler will automatically create the directory build/gen/bar if necessary, but it will not create build or build/gen; they must already exist.

Packages

The Objective-C code generated by the protocol buffer compiler is completely unaffected by the package name defined in the .proto file, as Objective-C has no language-enforced namespacing. Instead, Objective-C class names are distinguished using prefixes, which you can find out about in the next section.

Class prefix

Given the following file option:

option objc_class_prefix = "CGOOP";

The specified string - in this case, CGOOP - is prefixed in front of all Objective-C classes generated for this .proto file. Use prefixes that are 3 or more characters as recommended by Apple. Note that all 2 letter prefixes are reserved by Apple.

Camel case conversion

Idiomatic Objective-C uses camel case for all identifiers.

Messages will not have their names converted because the standard for proto files is to name messages in camel case already. It is assumed that the user has bypassed the convention for good reason, and the implementation will conform with their intentions.

Methods generated from field names and oneofs, enum declarations, and extension accessors will have their names camel cased. In general to convert from a proto name to a camel cased Objective-C name:

  • The first letter converted to uppercase (except for fields, which always start with a lowercase letter).
  • For each underscore in the name, the underscore is removed, and the following letter is capitalized.

So, for example, the field foo_bar_baz becomes fooBarBaz. The field FOO_bar becomes fooBar.

Messages

Given a simple message declaration:

message Foo {}

The protocol buffer compiler generates a class called Foo. If you specify an objc_class_prefix file option, the value of this option is prepended to the generated class name.

In the case of outer messages that have names matching any C/C++ or Objective-C keywords:

message static {}

the generated interfaces are suffixed by _Class, as follows:

@interface static_Class {}

Note that as per the camel case conversion rules the name static is not converted. In the case of an inner message that has a camel cased name that is FieldNumber or OneOfCase, the generated interface will be the camel cased name suffixed by _Class to make sure that the generated names do not conflict with the FieldNumber enumerations or OneOfCase enumerations.

A message can also be declared inside another message.

message Foo {
  message Bar {}
}

This generates:

@interface Foo_Bar : GPBMessage
@end

As you can see, the generated nested message name is the name of the generated containing message name (Foo) appended with underscore (_) and the nested message name (Bar).

Note: While we have tried to ensure that conflicts are kept to a minimum, there are still potential cases where message names may conflict due to the conversion between underscores and camel case. As an example:

message foo_bar {}
message foo { message bar {} }

will both generate @interface foo_bar and will conflict. The most pragmatic solution may be to rename the conflicting messages.

GPBMessage interface

GPBMessage is the superclass of all generated message classes. It is required to support a superset of the following interface:

@interface GPBMessage : NSObject
@end

The behaviors for this interface are as follows:

// Will do a deep copy.
- (id)copy;
// Will perform a deep equality comparison.
- (BOOL)isEqual:(id)value;

Unknown fields (proto2 only)

If a message created with an older version of your .proto definition is parsed with code generated from a newer version (or vice versa), the message may contain optional or repeated fields that the "new" code does not recognize. In proto2 generated code, these fields are not discarded and are stored in the message’s unknownFields property.

@property(nonatomic, copy, nullable) GPBUnknownFieldSet *unknownFields;

You can use the GPBUnknownFieldSet interface to fetch these fields by number or loop over them as an array.

In proto3, unknown fields are simply discarded when a message is parsed.

Fields

The following sections describe the code generated by the protocol buffer compiler for message fields.

Singular fields (proto3)

For every singular field the compiler generates a property to store data and an integer constant containing the field number. Message type fields also get a has.. property that lets you check if the field is set in the encoded message. So, for example, given the following message:

message Foo {
  message Bar {
    int32 int32_value = 1;
  }
  enum Qux {...}
  int32 int32_value = 1;
  string string_value = 2;
  Bar message_value = 3;
  Qux enum_value = 4;
  bytes bytes_value = 5;
}

The compiler will generate the following:

typedef GPB_ENUM(Foo_Bar_FieldNumber) {
  // The generated field number name is the enclosing message names delimited by
  // underscores followed by "FieldNumber", followed by the field name
  // camel cased.
  Foo_Bar_FieldNumber_Int32Value = 1,
};

@interface Foo_Bar : GPBMessage
@property(nonatomic, readwrite) int32_t int32Value;
@end

typedef GPB_ENUM(Foo_FieldNumber) {
  Foo_FieldNumber_Int32Value = 1,
  Foo_FieldNumber_StringValue = 2,
  Foo_FieldNumber_MessageValue = 3,
  Foo_FieldNumber_EnumValue = 4,
  Foo_FieldNumber_BytesValue = 5,
};

typedef GPB_ENUM(Foo_Qux) {
  Foo_Qux_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
  ...
};

@interface Foo : GPBMessage
// Field names are camel cased.
@property(nonatomic, readwrite) int32_t int32Value;
@property(nonatomic, readwrite, copy, null_resettable) NSString *stringValue;
@property(nonatomic, readwrite) BOOL hasMessageValue;
@property(nonatomic, readwrite, strong, null_resettable) Foo_Bar *messageValue;
@property(nonatomic, readwrite) Foo_Qux enumValue;
@property(nonatomic, readwrite, copy, null_resettable) NSData *bytesValue;
@end

Special naming cases

There are cases where the field name generation rules may result in name conflicts and names will need to be "uniqued". Such conflicts are resolved by appending _p to the end of the field (_p was selected because it’s pretty unique, and stands for "property").

message Foo {
  int32 foo_array = 1;      // Ends with Array
  int32 bar_OneOfCase = 2;  // Ends with oneofcase
  int32 id = 3;             // Is a C/C++/Objective-C keyword
}

generates:

typedef GPB_ENUM(Foo_FieldNumber) {
  // If a non-repeatable field name ends with "Array" it will be suffixed
  // with "_p" to keep the name distinct from repeated types.
  Foo_FieldNumber_FooArray_p = 1,
  // If a field name ends with "OneOfCase" it will be suffixed with "_p" to
  // keep the name distinct from OneOfCase properties.
  Foo_FieldNumber_BarOneOfCase_p = 2,
  // If a field name is a C/C++/ObjectiveC keyword it will be suffixed with
  // "_p" to allow it to compile.
  Foo_FieldNumber_Id_p = 3,
};

@interface Foo : GPBMessage
@property(nonatomic, readwrite) int32_t fooArray_p;
@property(nonatomic, readwrite) int32_t barOneOfCase_p;
@property(nonatomic, readwrite) int32_t id_p;
@end

Default values

The default value for numeric types is 0.

The default value for strings is @"", and the default value for bytes is [NSData data].

Assigning nil to a string field will assert in debug, and set the field to @"" in release. Assigning nil to a bytes field will assert in debug and set the field to [NSData data] in release. To test whether a bytes or string field is set requires testing its length property and comparing it to 0.

The default "empty" value for a message is an instance of the default message. To clear a message value it should be set to nil. Accessing a cleared message will return an instance of the default message and the hasFoo method will return false.

The default message returned for a field is a local instance. The reason behind returning a default message instead of nil is that in the case of:

message Foo {
  message Bar {
     int32 b;
  }
  Bar a;
}

The implementation will support:

Foo *foo = [[Foo alloc] init];
foo.a.b = 2;

where a will be automatically created via the accessors if necessary. If foo.a returned nil, the foo.a.b setter pattern would not work.

Singular fields (proto2)

For every singular field the compiler generates a property to store data, an integer constant containing the field number, and a has.. property that lets you check if the field is set in the encoded message. So, for example, given the following message:

message Foo {
  message Bar {
    int32 int32_value = 1;
  }
  enum Qux {...}
  optional int32 int32_value = 1;
  optional string string_value = 2;
  optional Bar message_value = 3;
  optional Qux enum_value = 4;
  optional bytes bytes_value = 5;
}

The compiler will generate the following:

# Enum Foo_Qux

typedef GPB_ENUM(Foo_Qux) {
  Foo_Qux_Flupple = 0,
};

GPBEnumDescriptor *Foo_Qux_EnumDescriptor(void);

BOOL Foo_Qux_IsValidValue(int32_t value);

# Message Foo

typedef GPB_ENUM(Foo_FieldNumber) {
  Foo_FieldNumber_Int32Value = 2,
  Foo_FieldNumber_MessageValue = 3,
  Foo_FieldNumber_EnumValue = 4,
  Foo_FieldNumber_BytesValue = 5,
  Foo_FieldNumber_StringValue = 6,
};

@interface Foo : GPBMessage

@property(nonatomic, readwrite) BOOL hasInt32Value;
@property(nonatomic, readwrite) int32_t int32Value;

@property(nonatomic, readwrite) BOOL hasStringValue;
@property(nonatomic, readwrite, copy, null_resettable) NSString *stringValue;

@property(nonatomic, readwrite) BOOL hasMessageValue;
@property(nonatomic, readwrite, strong, null_resettable) Foo_Bar *messageValue;

@property(nonatomic, readwrite) BOOL hasEnumValue;
@property(nonatomic, readwrite) Foo_Qux enumValue;

@property(nonatomic, readwrite) BOOL hasBytesValue;
@property(nonatomic, readwrite, copy, null_resettable) NSData *bytesValue;

@end

# Message Foo_Bar

typedef GPB_ENUM(Foo_Bar_FieldNumber) {
  Foo_Bar_FieldNumber_Int32Value = 1,
};

@interface Foo_Bar : GPBMessage

@property(nonatomic, readwrite) BOOL hasInt32Value;
@property(nonatomic, readwrite) int32_t int32Value;

@end

Special naming cases

There are cases where the field name generation rules may result in name conflicts and names will need to be "uniqued". Such conflicts are resolved by appending _p to the end of the field (_p was selected because it’s pretty unique, and stands for "property").

message Foo {
  optional int32 foo_array = 1;      // Ends with Array
  optional int32 bar_OneOfCase = 2;  // Ends with oneofcase
  optional int32 id = 3;             // Is a C/C++/Objective-C keyword
}

generates:

typedef GPB_ENUM(Foo_FieldNumber) {
  // If a non-repeatable field name ends with "Array" it will be suffixed
  // with "_p" to keep the name distinct from repeated types.
  Foo_FieldNumber_FooArray_p = 1,
  // If a field name ends with "OneOfCase" it will be suffixed with "_p" to
  // keep the name distinct from OneOfCase properties.
  Foo_FieldNumber_BarOneOfCase_p = 2,
  // If a field name is a C/C++/ObjectiveC keyword it will be suffixed with
  // "_p" to allow it to compile.
  Foo_FieldNumber_Id_p = 3,
};

@interface Foo : GPBMessage
@property(nonatomic, readwrite) int32_t fooArray_p;
@property(nonatomic, readwrite) int32_t barOneOfCase_p;
@property(nonatomic, readwrite) int32_t id_p;
@end

Default values (optional fields only)

The default value for numeric types, if no explicit default was specified by the user, is 0.

The default value for strings is @"", and the default value for bytes is [NSData data].

Assigning nil to a string field will assert in debug, and set the field to @"" in release. Assigning nil to a bytes field will assert in debug and set the field to [NSData data] in release. To test whether a bytes or string field is set requires testing its length property and comparing it to 0.

The default "empty" value for a message is an instance of the default message. To clear a message value it should be set to nil. Accessing a cleared message will return an instance of the default message and the hasFoo method will return false.

The default message returned for a field is a local instance. The reason behind returning a default message instead of nil is that in the case of:

message Foo {
  message Bar {
     int32 b;
  }
  Bar a;
}

The implementation will support:

Foo *foo = [[Foo alloc] init];
foo.a.b = 2;

where a will be automatically created via the accessors if necessary. If foo.a returned nil, the foo.a.b setter pattern would not work.

Repeated fields

Like singular fields(proto2 proto3), the protocol buffer compiler generates one data property for each repeated field. This data property is a GPB<VALUE>Array depending on the field type where <VALUE> can be one of UInt32, Int32, UInt64, Int64, Bool, Float, Double, or Enum. NSMutableArray will be used for string, bytes and message types. Field names for repeated types have Array appended to them. The reason for appending Array in the Objective-C interface is to make the code more readable. Repeated fields in proto files tend to have singular names which do not read well in standard Objective-C usage. Making the singular names plural would be more idiomatic Objective-C, however pluralization rules are too complex to support in the compiler.

message Foo {
  message Bar {}
  enum Qux {}
  repeated int32 int32_value = 1;
  repeated string string_value = 2;
  repeated Bar message_value = 3;
  repeated Qux enum_value = 4;
}

generates:

typedef GPB_ENUM(Foo_FieldNumber) {
  Foo_FieldNumber_Int32ValueArray = 1,
  Foo_FieldNumber_StringValueArray = 2,
  Foo_FieldNumber_MessageValueArray = 3,
  Foo_FieldNumber_EnumValueArray = 4,
};

@interface Foo : GPBMessage
// Field names for repeated types are the camel case name with
// "Array" suffixed.
@property(nonatomic, readwrite, strong, null_resettable)
 GPBInt32Array *int32ValueArray;
@property(nonatomic, readonly) NSUInteger int32ValueArray_Count;

@property(nonatomic, readwrite, strong, null_resettable)
 NSMutableArray *stringValueArray;
@property(nonatomic, readonly) NSUInteger stringValueArray_Count;

@property(nonatomic, readwrite, strong, null_resettable)
 NSMutableArray *messageValueArray;
@property(nonatomic, readonly) NSUInteger messageValueArray_Count;

@property(nonatomic, readwrite, strong, null_resettable)
 GPBEnumArray *enumValueArray;
@property(nonatomic, readonly) NSUInteger enumValueArray_Count;
@end

For string, bytes and message fields, elements of the array are NSString*, NSData* and pointers to subclasses of GPBMessage respectively.

Default values

The default value for a repeated field is to be empty. In Objective-C generated code, this is an empty GPB<VALUE>Array. If you access an empty repeated field, you’ll get back an empty array that you can update like any other repeated field array.

Foo *myFoo = [[Foo alloc] init];
[myFoo.stringValueArray addObject:@"A string"]

You can also use the provided <field>Array_Count property to check if the array for a particular repeated field is empty without having to create the array:

if (myFoo.messageValueArray_Count) {
  // There is something in the array...
}

GPB<VALUE>Array interface

GPB<VALUE>Arrays (aside from GPBEnumArray, which we’ll look at below) have the following interface:

@interface GPBArray : NSObject
@property (nonatomic, readonly) NSUInteger count;
+ (instancetype)array;
+ (instancetype)arrayWithValue:()value;
+ (instancetype)arrayWithValueArray:(GPBArray *)array;
+ (instancetype)arrayWithCapacity:(NSUInteger)count;

// Initializes the array, copying the values.
- (instancetype)initWithValueArray:(GPBArray *)array;
- (instancetype)initWithValues:(const  [])values
                         count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCapacity:(NSUInteger)count;

- ()valueAtIndex:(NSUInteger)index;

- (void)enumerateValuesWithBlock:
     (void (^)( value, NSUInteger idx, BOOL *stop))block;
- (void)enumerateValuesWithOptions:(NSEnumerationOptions)opts
    usingBlock:(void (^)( value, NSUInteger idx, BOOL *stop))block;

- (void)addValue:()value;
- (void)addValues:(const  [])values count:(NSUInteger)count;
- (void)addValuesFromArray:(GPBArray *)array;

- (void)removeValueAtIndex:(NSUInteger)count;
- (void)removeAll;

- (void)exchangeValueAtIndex:(NSUInteger)idx1
            withValueAtIndex:(NSUInteger)idx2;
- (void)insertValue:()value atIndex:(NSUInteger)count;
- (void)replaceValueAtIndex:(NSUInteger)index withValue:()value;


@end

GPBEnumArray has a slightly different interface to handle the validation function and to access raw values.

@interface GPBEnumArray : NSObject
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) GPBEnumValidationFunc validationFunc;

+ (instancetype)array;
+ (instancetype)arrayWithValidationFunction:(nullable GPBEnumValidationFunc)func;
+ (instancetype)arrayWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                   rawValue:value;
+ (instancetype)arrayWithValueArray:(GPBEnumArray *)array;
+ (instancetype)arrayWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                   capacity:(NSUInteger)count;

- (instancetype)initWithValidationFunction:
  (nullable GPBEnumValidationFunc)func;

// Initializes the array, copying the values.
- (instancetype)initWithValueArray:(GPBEnumArray *)array;
- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func
    values:(const int32_t [])values
    count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                  capacity:(NSUInteger)count;

// These will return kGPBUnrecognizedEnumeratorValue if the value at index
// is not a valid enumerator as defined by validationFunc. If the actual
// value is desired, use the "raw" version of the method.
- (int32_t)valueAtIndex:(NSUInteger)index;
- (void)enumerateValuesWithBlock:
    (void (^)(int32_t value, NSUInteger idx, BOOL *stop))block;
- (void)enumerateValuesWithOptions:(NSEnumerationOptions)opts
    usingBlock:(void (^)(int32_t value, NSUInteger idx, BOOL *stop))block;

// These methods bypass the validationFunc to provide access to values
// that were not known at the time the binary was compiled.
- (int32_t)rawValueAtIndex:(NSUInteger)index;

- (void)enumerateRawValuesWithBlock:
    (void (^)(int32_t value, NSUInteger idx, BOOL *stop))block;
- (void)enumerateRawValuesWithOptions:(NSEnumerationOptions)opts
    usingBlock:(void (^)(int32_t value, NSUInteger idx, BOOL *stop))block;

// If value is not a valid enumerator as defined by validationFunc, these
// methods will assert in debug, and will log in release and assign the value
// to the default value. Use the rawValue methods below to assign
// non enumerator values.
- (void)addValue:(int32_t)value;
- (void)addValues:(const int32_t [])values count:(NSUInteger)count;
- (void)insertValue:(int32_t)value atIndex:(NSUInteger)count;
- (void)replaceValueAtIndex:(NSUInteger)index withValue:(int32_t)value;

// These methods bypass the validationFunc to provide setting of values that
// were not known at the time the binary was compiled.
- (void)addRawValue:(int32_t)rawValue;
- (void)addRawValuesFromEnumArray:(GPBEnumArray *)array;
- (void)addRawValues:(const int32_t [])values count:(NSUInteger)count;
- (void)replaceValueAtIndex:(NSUInteger)index withRawValue:(int32_t)rawValue;
- (void)insertRawValue:(int32_t)value atIndex:(NSUInteger)count;

// No validation applies to these methods.
- (void)removeValueAtIndex:(NSUInteger)count;
- (void)removeAll;
- (void)exchangeValueAtIndex:(NSUInteger)idx1
            withValueAtIndex:(NSUInteger)idx2;

@end

Oneof fields

Given a message with oneof field definitions:

message Order {
  oneof OrderID {
    string name = 1;
    int32 address = 2;
  };
  int32 quantity = 3;
};

The protocol buffer compiler generates:

typedef GPB_ENUM(Order_OrderID_OneOfCase) {
  Order_OrderID_OneOfCase_GPBUnsetOneOfCase = 0,
  Order_OrderID_OneOfCase_Name = 1,
  Order_OrderID_OneOfCase_Address = 2,
};

typedef GPB_ENUM(Order_FieldNumber) {
  Order_FieldNumber_Name = 1,
  Order_FieldNumber_Address = 2,
  Order_FieldNumber_Quantity = 3,
};

@interface Order : GPBMessage
@property (nonatomic, readwrite) Order_OrderID_OneOfCase orderIDOneOfCase;
@property (nonatomic, readwrite, copy, null_resettable) NSString *name;
@property (nonatomic, readwrite) int32_t address;
@property (nonatomic, readwrite) int32_t quantity;
@end

void Order_ClearOrderIDOneOfCase(Order *message);

Setting one of the oneof properties will clear all the other properties associated with the oneof.

<ONE_OF_NAME>_OneOfCase_GPBUnsetOneOfCase will always be equivalent to 0 to allow for easy testing to see if any field in the oneof is set.

Map Fields

For this message definition:

message Bar {...}
message Foo {
  map<int32, string> a_map = 1;
  map<string, Bar> b_map = 2;
};

The compiler generates the following:

typedef GPB_ENUM(Foo_FieldNumber) {
  Foo_FieldNumber_AMap = 1,
  Foo_FieldNumber_BMap = 2,
};

@interface Foo : GPBMessage
// Map names are the camel case version of the field name.
@property (nonatomic, readwrite, strong, null_resettable) GPBInt32ObjectDictionary *aMap;
@property(nonatomic, readonly) NSUInteger aMap_Count;
@property (nonatomic, readwrite, strong, null_resettable) NSMutableDictionary *bMap;
@property(nonatomic, readonly) NSUInteger bMap_Count;
@end

Cases where keys are strings and values are strings, bytes, or messages are handled by NSMutableDictionary.

Other cases are:

GBP<KEY><VALUE>Dictionary

where:

  • <KEY> is Uint32, Int32, UInt64, Int64, Bool or String.
  • <VALUE> is UInt32, Int32, UInt64, Int64, Bool, Float, Double, Enum, or Object. Object is used for values of type string bytes or message to cut down on the number of classes and is in line with how Objective-C works with NSMutableDictionary.

Default values

The default value for a map field is empty. In Objective-C generated code, this is an empty GBP<KEY><VALUE>Dictionary. If you access an empty map field, you’ll get back an empty dictionary that you can update like any other map field.

You can also use the provided <mapField>_Count property to check if a particular map is empty:

if (myFoo.myMap_Count) {
  // There is something in the map...
}

GBP<KEY><VALUE>Dictionary interface

The GBP<KEY><VALUE>Dictionary (apart from GBP<KEY>ObjectDictionary and GBP<KEY>EnumDictionary) interface is as follows:

@interface GPB<KEY>Dictionary : NSObject
@property (nonatomic, readonly) NSUInteger count;

+ (instancetype)dictionary;
+ (instancetype)dictionaryWithValue:(const )value
                             forKey:(const <KEY>)key;
+ (instancetype)dictionaryWithValues:(const  [])values
                             forKeys:(const <KEY> [])keys
                               count:(NSUInteger)count;
+ (instancetype)dictionaryWithDictionary:(GPB<KEY>Dictionary *)dictionary;
+ (instancetype)dictionaryWithCapacity:(NSUInteger)numItems;

- (instancetype)initWithValues:(const  [])values
                       forKeys:(const <KEY> [])keys
                         count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDictionary:(GPB<KEY>Dictionary *)dictionary;
- (instancetype)initWithCapacity:(NSUInteger)numItems;

- (BOOL)valueForKey:(<KEY>)key value:(VALUE *)value;

- (void)enumerateKeysAndValuesUsingBlock:
    (void (^)(<KEY> key,  value, BOOL *stop))block;

- (void)removeValueForKey:(<KEY>)aKey;
- (void)removeAll;
- (void)setValue:()value forKey:(<KEY>)key;
- (void)addEntriesFromDictionary:(GPB<KEY>Dictionary *)otherDictionary;
@end

The GBP<KEY>ObjectDictionary interface is:

@interface GPB<KEY>ObjectDictionary : NSObject
@property (nonatomic, readonly) NSUInteger count;

+ (instancetype)dictionary;
+ (instancetype)dictionaryWithObject:(id)object
                             forKey:(const <KEY>)key;
+ (instancetype)
  dictionaryWithObjects:(const id GPB_UNSAFE_UNRETAINED [])objects
                forKeys:(const <KEY> [])keys
                  count:(NSUInteger)count;
+ (instancetype)dictionaryWithDictionary:(GPB<KEY>ObjectDictionary *)dictionary;
+ (instancetype)dictionaryWithCapacity:(NSUInteger)numItems;

- (instancetype)initWithObjects:(const id GPB_UNSAFE_UNRETAINED [])objects
                        forKeys:(const <KEY> [])keys
                          count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDictionary:(GPB<KEY>ObjectDictionary *)dictionary;
- (instancetype)initWithCapacity:(NSUInteger)numItems;

- (id)objectForKey:(uint32_t)key;

- (void)enumerateKeysAndObjectsUsingBlock:
    (void (^)(<KEY> key, id object, BOOL *stop))block;

- (void)removeObjectForKey:(<KEY>)aKey;
- (void)removeAll;
- (void)setObject:(id)object forKey:(<KEY>)key;
- (void)addEntriesFromDictionary:(GPB<KEY>ObjectDictionary *)otherDictionary;
@end

GBP<KEY>EnumDictionary has a slightly different interface to handle the validation function and to access raw values.

@interface GPB<KEY>EnumDictionary : NSObject

@property(nonatomic, readonly) NSUInteger count;
@property(nonatomic, readonly) GPBEnumValidationFunc validationFunc;

+ (instancetype)dictionary;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                        rawValue:(int32_t)rawValue
                                          forKey:(<KEY>_t)key;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                       rawValues:(const int32_t [])values
                                         forKeys:(const <KEY>_t [])keys
                                           count:(NSUInteger)count;
+ (instancetype)dictionaryWithDictionary:(GPB<KEY>EnumDictionary *)dictionary;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                        capacity:(NSUInteger)numItems;

- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func;
- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                 rawValues:(const int32_t [])values
                                   forKeys:(const <KEY>_t [])keys
                                     count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDictionary:(GPB<KEY>EnumDictionary *)dictionary;
- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                  capacity:(NSUInteger)numItems;

// These will return kGPBUnrecognizedEnumeratorValue if the value for the key
// is not a valid enumerator as defined by validationFunc. If the actual value is
// desired, use "raw" version of the method.

- (BOOL)valueForKey:(<KEY>_t)key value:(nullable int32_t *)value;

- (void)enumerateKeysAndValuesUsingBlock:
    (void (^)(<KEY>_t key, int32_t value, BOOL *stop))block;

// These methods bypass the validationFunc to provide access to values that were not
// known at the time the binary was compiled.

- (BOOL)valueForKey:(<KEY>_t)key rawValue:(nullable int32_t *)rawValue;

- (void)enumerateKeysAndRawValuesUsingBlock:
    (void (^)(<KEY>_t key, int32_t rawValue, BOOL *stop))block;

- (void)addRawEntriesFromDictionary:(GPB<KEY>EnumDictionary *)otherDictionary;

// If value is not a valid enumerator as defined by validationFunc, these
// methods will assert in debug, and will log in release and assign the value
// to the default value. Use the rawValue methods below to assign non enumerator
// values.

- (void)setValue:(int32_t)value forKey:(<KEY>_t)key;

// This method bypass the validationFunc to provide setting of values that were not
// known at the time the binary was compiled.
- (void)setRawValue:(int32_t)rawValue forKey:(<KEY>_t)key;

// No validation applies to these methods.

- (void)removeValueForKey:(<KEY>_t)aKey;
- (void)removeAll;

@end

Enumerations

Given an enum definition like:

enum Foo {
  VALUE_A = 0;
  VALUE_B = 1;
  VALUE_C = 5;
}

the generated code will be:

// The generated enum value name will be the enumeration name followed by
// an underscore and then the enumerator name converted to camel case.
// GPB_ENUM is a macro defined in the Objective-C Protocol Buffer headers
// that enforces all enum values to be int32 and aids in Swift Enumeration
// support.
typedef GPB_ENUM(Foo) {
  Foo_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue, //proto3 only
  Foo_ValueA = 0,
  Foo_ValueB = 1;
  Foo_ValueC = 5;
};

// Returns information about what values this enum type defines.
GPBEnumDescriptor *Foo_EnumDescriptor();

Each enumeration has a validation function declared for it:

// Returns YES if the given numeric value matches one of Foo's
// defined values (0, 1, 5).
BOOL Foo_IsValidValue(int32_t value);

and an enumeration descriptor accessor function declared for it:

// GPBEnumDescriptor is defined in the runtime and contains information
// about the enum definition, such as the enum name, enum value and enum value
// validation function.
typedef GPBEnumDescriptor *(*GPBEnumDescriptorAccessorFunc)();

The enum descriptor accessor functions are C functions, as opposed to methods on the enumeration class, because they are rarely used by client software. This will cut down on the amount of Objective-C runtime information generated, and potentially allow the linker to deadstrip them.

In the case of outer enums that have names matching any C/C++ or Objective-C keywords, such as:

enum method {}

the generated interfaces are suffixed with _Enum, as follows:

// The generated enumeration name is the keyword suffixed by _Enum.
typedef GPB_ENUM(Method_Enum) {}

An enum can also be declared inside another message. For example:

message Foo {
  enum Bar {
    VALUE_A = 0;
    VALUE_B = 1;
    VALUE_C = 5;
  }
  Bar aBar = 1;
  Bar aDifferentBar = 2;
  repeated Bar aRepeatedBar = 3;
}

generates:

typedef GPB_ENUM(Foo_Bar) {
  Foo_Bar_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue, //proto3 only
  Foo_Bar_ValueA = 0;
  Foo_Bar_ValueB = 1;
  Foo_Bar_ValueC = 5;
};

GPBEnumDescriptor *Foo_Bar_EnumDescriptor();

BOOL Foo_Bar_IsValidValue(int32_t value);

@interface Foo : GPBMessage
@property (nonatomic, readwrite) Foo_Bar aBar;
@property (nonatomic, readwrite) Foo_Bar aDifferentBar;
@property (nonatomic, readwrite, strong, null_resettable)
 GPBEnumArray *aRepeatedBarArray;
@end

// proto3 only Every message that has an enum field will have an accessor function to get
// the value of that enum as an integer. This allows clients to deal with
// raw values if they need to.
int32_t Foo_ABar_RawValue(Foo *message);
void SetFoo_ABar_RawValue(Foo *message, int32_t value);
int32_t Foo_ADifferentBar_RawValue(Foo *message);
void SetFoo_ADifferentBar_RawValue(Foo *message, int32_t value);

All enumeration fields have the ability to access the value as a typed enumerator (Foo_Bar in the example above), or, if using proto3, as a raw int32_t value (using the accessor functions in the example above). This is to support the case where the server returns values that the client may not recognize due to the client and server being compiled with different versions of the proto file.

Unrecognized enum values are treated differently depending on which protocol buffers version you are using. In proto3, kGPBUnrecognizedEnumeratorValue is returned for the typed enumerator value if the enumerator value in the parsed message data is not one that the code reading it was compiled to support. If the actual value is desired, use the raw value accessors to get the value as an int32_t. If you are using proto2, unrecognized enum values are treated as unknown fields.

kGPBUnrecognizedEnumeratorValue is defined as 0xFBADBEEF, and it will be an error if any enumerator in an enumeration has this value. Attempting to set any enumeration field to this value is a runtime error. Similarly, attempting to set any enumeration field to an enumerator not defined by its enumeration type using the typed accessors is a runtime error. In both error cases, debug builds will cause an assertion and release builds will log and set the field to its default value (0).

The raw value accessors are defined as C functions instead of as Objective-C methods because they are not used in most cases. Declaring them as C functions cuts down on wasted Objective-C runtime information and allows the linker to potentially dead strip them.

Swift Enumeration Support

Apple documents how they import Objective-C enumerations to Swift enumerations in Interacting with C APIs. Protocol buffer-generated enumerations support Objective-C to Swift conversions.

// Proto
enum Foo {
  VALUE_A = 0;
}

generates:

// Objective-C
typedef GPB_ENUM(Foo) {
  Foo_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
  Foo_ValueA = 0,
};

which in Swift code will allow:

// Swift
let aValue = Foo.ValueA
let anotherValue: Foo = .GPBUnrecognizedEnumeratorValue

Well-known types (proto3 only)

If you use any of the message types provided with proto3, they will in general just use their proto definitions in generated Objective-C code, though we supply some basic conversion methods in categories to make using them simpler. Note that we do not have special APIs for all well-known types yet, including Any (there is currently no helper method to convert an Any’s message value into a message of the appropriate type).

Time Stamps

@interface GPBTimeStamp (GPBWellKnownTypes)
@property (nonatomic, readwrite, strong) NSDate *date;
@property (nonatomic, readwrite) NSTimeInterval timeIntervalSince1970;
- (instancetype)initWithDate:(NSDate *)date;
- (instancetype)initWithTimeIntervalSince1970:
    (NSTimeInterval)timeIntervalSince1970;
@end

Duration

@interface GPBDuration (GPBWellKnownTypes)
@property (nonatomic, readwrite) NSTimeInterval timeIntervalSince1970;
- (instancetype)initWithTimeIntervalSince1970:
    (NSTimeInterval)timeIntervalSince1970;
@end

Extensions (proto2 only)

Given a message with an extension range:

message Foo {
  extensions 100 to 199;
}

extend Foo {
  optional int32 foo = 101;
  repeated int32 repeated_foo = 102;
}

message Bar {
  extend Foo {
    optional int32 bar = 103;
    repeated int32 repeated_bar = 104;
  }
}

The compiler generates the following:

# File Test2Root

@interface Test2Root : GPBRootObject

// The base class provides:
//   + (GPBExtensionRegistry *)extensionRegistry;
// which is an GPBExtensionRegistry that includes all the extensions defined by
// this file and all files that it depends on.

@end

@interface Test2Root (DynamicMethods)
+ (GPBExtensionDescriptor *)foo;
+ (GPBExtensionDescriptor *)repeatedFoo;
@end

# Message Foo

@interface Foo : GPBMessage

@end

# Message Bar

@interface Bar : GPBMessage

@end

@interface Bar (DynamicMethods)

+ (GPBExtensionDescriptor *)bar;
+ (GPBExtensionDescriptor *)repeatedBar;
@end

To get and set these extension fields, you use the following:

Foo *fooMsg = [[Foo alloc] init];

// Set the single field extensions
[fooMsg setExtension:[Test2Root foo] value:@5];
NSAssert([fooMsg hasExtension:[Test2Root foo]]);
NSAssert([[fooMsg getExtension:[Test2Root foo]] intValue] == 5);

// Add two things to the repeated extension:
[fooMsg addExtension:[Test2Root repeatedFoo] value:@1];
[fooMsg addExtension:[Test2Root repeatedFoo] value:@2];
NSAssert([fooMsg hasExtension:[Test2Root repeatedFoo]]);
NSAssert([[fooMsg getExtension:[Test2Root repeatedFoo]] count] == 2);

// Clearing
[fooMsg clearExtension:[Test2Root foo]];
[fooMsg clearExtension:[Test2Root repeatedFoo]];
NSAssert(![fooMsg hasExtension:[Test2Root foo]]);
NSAssert(![fooMsg hasExtension:[Test2Root repeatedFoo]]);