Objective-C 生成代码指南

准确描述了 Protocol Buffer 编译器为任何给定的协议定义生成的 Objective-C 代码。

突出显示了 proto2 和 proto3 生成代码之间的任何差异。在阅读本文档之前,您应该阅读proto2 语言指南和/或proto3 语言指南

编译器调用

当使用 --objc_out= 命令行标志调用时,Protocol Buffer 编译器会生成 Objective-C 输出。--objc_out= 选项的参数是您希望编译器将 Objective-C 输出写入的目录。编译器为每个 .proto 文件输入创建一个头文件和一个实现文件。输出文件的名称是通过获取 .proto 文件的名称并进行以下更改来计算的

  • 文件名是通过将 .proto 文件的基本名称转换为驼峰式大小写来确定的。例如,foo_bar.proto 将变为 FooBar
  • 扩展名 (.proto) 分别替换为头文件或实现文件的 pbobjc.hpbobjc.m
  • proto 路径(使用 --proto_path=-I 命令行标志指定)替换为输出路径(使用 --objc_out= 标志指定)。

因此,例如,如果您按如下方式调用编译器

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

编译器将读取文件 src/foo.protosrc/bar/baz.proto 并生成四个输出文件:build/gen/Foo.pbobjc.hbuild/gen/Foo.pbobjc.mbuild/gen/bar/Baz.pbobjc.hbuild/gen/bar/Baz.pbobjc.m。编译器将根据需要自动创建目录 build/gen/bar,但它不会创建 buildbuild/gen;它们必须已经存在。

Protocol Buffer 编译器生成的 Objective-C 代码完全不受 .proto 文件中定义的包名称的影响,因为 Objective-C 没有语言强制的命名空间。相反,Objective-C 类名使用前缀区分,您可以在下一节中了解有关前缀的信息。

类前缀

给定以下文件选项

option objc_class_prefix = "CGOOP";

指定的字符串 - 在这种情况下为 CGOOP - 将作为前缀添加到为该 .proto 文件生成的全部 Objective-C 类之前。按照 Apple 的建议,使用 3 个或更多字符的前缀。请注意,所有 2 个字母的前缀都被 Apple 保留。

驼峰式转换

惯用的 Objective-C 对所有标识符使用驼峰式大小写。

消息不会转换其名称,因为 proto 文件的标准是已经以驼峰式大小写命名消息。假设用户出于充分的理由绕过了约定,并且实现将符合其意图。

从字段名称和 oneofenum 声明以及扩展访问器生成的 method 将转换为驼峰式大小写。通常,要从 proto 名称转换为驼峰式大小写的 Objective-C 名称

  • 将第一个字母转换为大写(字段除外,字段始终以小写字母开头)。
  • 对于名称中的每个下划线,删除下划线,并将后面的字母大写。

因此,例如,字段 foo_bar_baz 变为 fooBarBaz。字段 FOO_bar 变为 fooBar

消息

给定一个简单的消息声明

message Foo {}

Protocol Buffer 编译器会生成一个名为 Foo 的类。如果指定了objc_class_prefix 文件选项,则此选项的值将附加到生成的类名前。

对于外部消息,如果其名称与任何 C/C++ 或 Objective-C 关键字匹配

message static {}

生成的接口将附加 _Class,如下所示

@interface static_Class {}

请注意,根据驼峰式转换规则,名称 static 不会被转换。对于内部消息,如果其驼峰式名称为 FieldNumberOneOfCase,则生成的接口将为驼峰式名称附加 _Class,以确保生成的名称不会与 FieldNumber 枚举或 OneOfCase 枚举冲突。

消息也可以在另一个消息中声明。

message Foo {
  message Bar {}
}

这将生成

@interface Foo_Bar : GPBMessage
@end

如您所见,生成的嵌套消息名称是生成的包含消息名称 (Foo) 附加下划线 (_) 和嵌套消息名称 (Bar)。

注意:虽然我们已尽力确保将冲突降到最低,但仍然存在一些潜在的情况,即由于下划线和驼峰式大小写之间的转换,消息名称可能会发生冲突。例如

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

两者都将生成 @interface foo_bar 并发生冲突。最实用的解决方案可能是重命名冲突的消息。

GPBMessage 接口

GPBMessage 是所有生成的 message 类的超类。它需要支持以下接口的超集

@interface GPBMessage : NSObject
@end

此接口的行为如下

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

未知字段(仅限 proto2)

如果使用 .proto 定义的旧版本创建的消息使用从新版本(反之亦然)生成的代码进行解析,则消息可能包含“新”代码无法识别的可选或重复字段。在 proto2 生成的代码中,这些字段不会被丢弃,而是存储在消息的 unknownFields 属性中。

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

您可以使用 GPBUnknownFieldSet 接口按编号获取这些字段,或将其循环遍历为数组。

在 proto3 中,解析消息时会简单地丢弃未知字段。

字段

以下部分描述了 Protocol Buffer 编译器为 message 字段生成的代码。

单数字段(proto3)

对于每个单数字段,编译器都会生成一个属性来存储数据,以及一个包含字段编号的整数常量。消息类型字段还会获得一个 has.. 属性,使您可以检查该字段是否在编码的消息中设置。因此,例如,给定以下消息

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;
}

编译器将生成以下内容

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

特殊命名情况

在某些情况下,字段名称生成规则可能会导致名称冲突,并且需要“唯一化”名称。通过在字段末尾附加 _p 来解决此类冲突 (_p 被选中是因为它非常独特,并且代表“属性”)。

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
}

生成

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

默认值

数字类型的默认值0

字符串的默认值为 @"",字节的默认值为 [NSData data]

nil 分配给字符串字段将在调试中断言,并在发布版本中将字段设置为 @""。将 nil 分配给字节字段将在调试中断言,并在发布版本中将字段设置为 [NSData data]。要测试是否设置了字节或字符串字段,需要测试其长度属性并将其与 0 进行比较。

消息的默认“空”值为默认消息的实例。要清除消息值,应将其设置为 nil。访问已清除的消息将返回默认消息的实例,并且 hasFoo 方法将返回 false。

为字段返回的默认消息是本地实例。返回默认消息而不是 nil 的原因是,在以下情况下

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

实现将支持

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

其中 a 将在必要时通过访问器自动创建。如果 foo.a 返回 nil,则 foo.a.b setter 模式将无法工作。

单数字段(proto2)

对于每个单一字段,编译器都会生成一个属性来存储数据,一个包含字段编号的整数常量,以及一个has..属性,允许您检查编码消息中是否设置了该字段。例如,给定以下消息

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;
}

编译器将生成以下内容

# 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

特殊命名情况

在某些情况下,字段名称生成规则可能会导致名称冲突,并且需要“唯一化”名称。通过在字段末尾附加 _p 来解决此类冲突 (_p 被选中是因为它非常独特,并且代表“属性”)。

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
}

生成

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

默认值(仅可选字段)

如果用户未指定显式默认值,则数值类型的默认值0

字符串的默认值为 @"",字节的默认值为 [NSData data]

nil 分配给字符串字段将在调试中断言,并在发布版本中将字段设置为 @""。将 nil 分配给字节字段将在调试中断言,并在发布版本中将字段设置为 [NSData data]。要测试是否设置了字节或字符串字段,需要测试其长度属性并将其与 0 进行比较。

消息的默认“空”值为默认消息的实例。要清除消息值,应将其设置为 nil。访问已清除的消息将返回默认消息的实例,并且 hasFoo 方法将返回 false。

为字段返回的默认消息是本地实例。返回默认消息而不是 nil 的原因是,在以下情况下

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

实现将支持

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

其中 a 将在必要时通过访问器自动创建。如果 foo.a 返回 nil,则 foo.a.b setter 模式将无法工作。

重复字段

与单一字段(proto2 proto3)类似,协议缓冲区编译器为每个重复字段生成一个数据属性。此数据属性为GPB<VALUE>Array,具体取决于字段类型,其中<VALUE>可以是UInt32Int32UInt64Int64BoolFloatDoubleEnum。对于stringbytesmessage类型,将使用NSMutableArray。重复类型的字段名称后附加Array。在Objective-C接口中附加Array的原因是为了使代码更易读。proto文件中的重复字段往往具有单数名称,在标准Objective-C用法中读起来不太好。将单数名称变为复数将更符合Objective-C的习惯用法,但是复数化规则过于复杂,编译器无法支持。

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;
}

生成

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

对于字符串、字节和消息字段,数组的元素分别为NSString*NSData*GPBMessage子类的指针。

默认值

重复字段的默认值为空。在Objective-C生成的代码中,这是一个空的GPB<VALUE>Array。如果访问空重复字段,您将获得一个空数组,您可以像其他任何重复字段数组一样更新它。

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

您还可以使用提供的<field>Array_Count属性来检查特定重复字段的数组是否为空,而无需创建该数组。

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

GPB<VALUE>Array接口

GPB<VALUE>Array(除了GPBEnumArray,我们将在下面讨论)具有以下接口

@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具有略微不同的接口来处理验证函数并访问原始值。

@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 字段

给定一个具有oneof字段定义的消息

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

协议缓冲区编译器会生成

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);

设置oneof属性之一将清除与该oneof关联的所有其他属性。

<ONE_OF_NAME>_OneOfCase_GPBUnsetOneOfCase将始终等效于0,以便轻松测试以查看oneof中的任何字段是否已设置。

映射字段

对于此消息定义

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

编译器生成以下内容

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

键为字符串且值为字符串、字节或消息的情况由NSMutableDictionary处理。

其他情况是

GBP<KEY><VALUE>Dictionary

其中

  • <KEY>是Uint32、Int32、UInt64、Int64、Bool或String。
  • <VALUE>是UInt32、Int32、UInt64、Int64、Bool、Float、Double、Enum或Object。Object用于类型为stringbytesmessage的值,以减少类的数量,并且符合Objective-C使用NSMutableDictionary的方式。

默认值

映射字段的默认值为空。在Objective-C生成的代码中,这是一个空的GBP<KEY><VALUE>Dictionary。如果访问空映射字段,您将获得一个空字典,您可以像其他任何映射字段一样更新它。

您还可以使用提供的<mapField>_Count属性来检查特定映射是否为空。

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

GBP<KEY><VALUE>Dictionary接口

GBP<KEY><VALUE>Dictionary(除了GBP<KEY>ObjectDictionaryGBP<KEY>EnumDictionary)的接口如下

@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

GBP<KEY>ObjectDictionary接口为

@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具有略微不同的接口来处理验证函数并访问原始值。

@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

枚举

给定一个类似于以下的枚举定义

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

生成的代码将为

// 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();

每个枚举都声明了一个与其关联的验证函数

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

以及一个与其关联的枚举描述符访问器函数。

// 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)();

枚举描述符访问器函数是C函数,而不是枚举类上的方法,因为客户端软件很少使用它们。这将减少生成的Objective-C运行时信息量,并可能允许链接器删除它们。

对于名称与任何C/C++或Objective-C关键字匹配的外部枚举,例如

enum method {}

生成的接口后缀为_Enum,如下所示

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

枚举也可以在另一个消息内部声明。例如

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

生成

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);

所有枚举字段都能够以类型化枚举器(上例中的Foo_Bar)或(如果使用proto3)以原始int32_t值(使用上例中的访问器函数)访问值。这是为了支持服务器返回客户端可能无法识别值的情况,因为客户端和服务器使用proto文件的不同版本编译。

未识别的枚举值的处理方式取决于您使用的协议缓冲区版本。在proto3中,如果解析的消息数据中的枚举值不是读取它的代码编译以支持的值,则对于类型化枚举值将返回kGPBUnrecognizedEnumeratorValue。如果需要实际值,请使用原始值访问器以int32_t形式获取该值。如果您使用proto2,则未识别的枚举值将被视为未知字段。

kGPBUnrecognizedEnumeratorValue定义为0xFBADBEEF,如果枚举中的任何枚举器具有此值,则将发生错误。尝试将任何枚举字段设置为此值是运行时错误。类似地,尝试使用类型化访问器将任何枚举字段设置为其枚举类型未定义的枚举器也是运行时错误。在这两种错误情况下,调试版本将导致断言,而发布版本将记录并将其字段设置为其默认值(0)。

原始值访问器定义为C函数而不是Objective-C方法,因为在大多数情况下不使用它们。将它们声明为C函数可以减少浪费的Objective-C运行时信息,并允许链接器潜在地删除它们。

Swift 枚举支持

Apple在与C API交互中记录了如何将Objective-C枚举导入到Swift枚举中。协议缓冲区生成的枚举支持Objective-C到Swift的转换。

// Proto
enum Foo {
  VALUE_A = 0;
}

生成

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

这将在Swift代码中允许

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

众所周知的类型(仅限 proto3)

如果您使用proto3提供的任何消息类型,它们通常只会在其生成的Objective-C代码中使用其proto定义,尽管我们在类别中提供了一些基本的转换方法以使使用它们更简单。请注意,我们还没有为所有众所周知的类型提供特殊的API,包括Any(目前没有辅助方法可以将Any的消息值转换为适当类型的消息)。

时间戳

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

持续时间

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

扩展(仅限 proto2)

给定一个具有扩展范围的消息

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;
  }
}

编译器生成以下内容

# 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

要获取和设置这些扩展字段,请使用以下方法

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]]);