Objective-C 生成代码指南

精确描述协议缓冲区编译器为任何给定的协议定义生成的 Objective-C 代码。

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

编译器调用

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

  • 文件名称通过将 .proto 文件基名转换为驼峰式命名来确定。例如,foo_bar.proto 将变成 FooBar
  • 扩展名 (.proto) 被替换为头文件的 pbobjc.h 或实现文件的 pbobjc.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;它们必须已经存在。

包 (Packages)

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

类前缀

给定以下 文件选项

option objc_class_prefix = "CGOOP";

指定的字符串(在本例中为 CGOOP)将作为前缀添加到为此 .proto 文件生成的所有 Objective-C 类之前。使用 Apple 推荐的 3 个或更多字符的前缀。请注意,所有 2 个字符的前缀都由 Apple 保留。

驼峰式命名转换

地道的 Objective-C 对所有标识符都使用驼峰式命名。

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

从字段名和 oneofenum 声明和扩展访问器生成的方法的名称将被转换为驼峰式。通常,将 proto 名称转换为驼峰式 Objective-C 名称的方法是:

  • 第一个字母转换为大写(字段除外,字段总是以小写字母开头)。
  • 名称中的每个下划线都被移除,并且其后的字母大写。

例如,字段 foo_bar_baz 变成 fooBarBaz。字段 FOO_bar 变成 fooBar

消息 (Messages)

给定一个简单的消息声明:

message Foo {}

协议缓冲区编译器生成一个名为 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 是所有生成的 संदेश 类的超类。它必须支持以下接口的超集:

@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 中,当解析消息时,未知字段将被简单地丢弃。

字段 (Fields)

以下各节描述了协议缓冲区编译器为消息字段生成的代码。

单一字段 (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 是因为它非常独特,并且代表“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
}

生成:

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 是因为它非常独特,并且代表“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
}

生成:

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 中是否有任何字段已设置。

Map 字段

对于此消息定义:

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。为了减少类数量并与 Objective-C 使用 NSMutableDictionary 的方式一致,对类型为 stringbytesmessage 的值使用 Object

默认值

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

您还可以使用提供的 <mapField>_Count 属性检查特定 map 是否为空:

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

枚举 (Enumerations)

给定一个枚举定义,例如:

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

周知类型 (well-known types) (仅 proto3)

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

时间戳

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

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

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