Objective-C Generated Code Guide
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 becomeFooBar
. - The extension (
.proto
) is replaced with eitherpbobjc.h
orpbobjc.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 oneof
s, 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>Array
s (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 typestring
bytes
ormessage
to cut down on the number of classes and is in line with how Objective-C works withNSMutableDictionary
.
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]]);