Objective-C 生成代码指南

描述了 protocol buffer 编译器为任何给定的协议定义所生成的 Objective-C 代码的具体内容。

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

编译器调用

当使用 --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;它们必须已经存在。

包(Packages)

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

类前缀

给定以下文件选项

option objc_class_prefix = "CGOOP";

指定的字符串——在本例中为 CGOOP——被加在为该 .proto 文件生成的所有 Objective-C 类名的前面。请使用苹果推荐的 3 个或更多字符的前缀。请注意,所有 2 个字母的前缀都由苹果保留。

驼峰命名法转换

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

消息的名称不会被转换,因为 proto 文件的标准已经是使用驼峰命名法来命名消息。我们假定用户出于充分的理由绕过了这一约定,实现将遵从他们的意图。

从字段名和 oneofenum 声明以及扩展访问器生成的 方法名将被转换为驼峰命名法。通常,从 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 是所有生成的消息类的超类。它需要支持以下接口的超集:

@interface GPBMessage : NSObject
@end

此接口的行为如下:

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

未知字段

当解析消息时,它可能包含解析代码所不知道的字段。当消息是使用一个 .proto 定义的旧版本创建,然后被使用由新版本生成的代码解析时(或反之),就会发生这种情况。

这些字段不会被丢弃,而是存储在消息的 unknownFields 属性中:

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

您可以使用 GPBUnknownFieldSet 接口按编号获取这些字段,或将它们作为数组进行遍历。

字段

以下各节描述了 protocol buffer 编译器为消息字段生成的代码。它们按隐式和显式存在性进行划分。您可以在字段存在性中了解有关此区别的更多信息。

具有隐式存在性的单一字段

对于每个单一字段,编译器都会生成一个用于存储数据的属性和一个包含字段编号的整型常量。消息类型的字段还会得到一个 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]。要测试字节或字符串字段是否已设置,需要测试其 length 属性并与 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 返回 nilfoo.a.b 的设置器模式将无法工作。

具有显式存在性的单一字段

对于每个单一字段,编译器都会生成一个用于存储数据的属性、一个包含字段编号的整型常量,以及一个 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]。要测试字节或字符串字段是否已设置,需要测试其 length 属性并与 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 返回 nilfoo.a.b 的设置器模式将无法工作。

重复字段

与单一字段(proto2 proto3)类似,protocol buffer 编译器会为每个重复字段生成一个数据属性。此数据属性是一个 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

注意: 重复字段的行为可以在 Editions 中通过 features.repeated_field_encoding 特性进行配置。

对于字符串、字节和消息字段,数组的元素分别是 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;
};

protocol buffer 编译器会生成:

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 的方式一致。

默认值

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

枚举

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

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 运行时信息的数量,并可能允许链接器将其进行死代码剥离(deadstrip)。

如果外部枚举的名称与任何 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 文件编译的。

未识别的枚举值的处理方式取决于语言版本和 Editions 中的 features.enum_type 特性。

  • 在开放枚举中,如果解析的消息数据中的枚举器值不是读取该数据的代码所编译支持的值,则类型化枚举器值将返回 kGPBUnrecognizedEnumeratorValue。如果需要实际值,请使用原始值访问器以 int32_t 形式获取该值。
  • 在封闭枚举中,未识别的枚举值被视为未知字段。
  • Proto2 枚举是封闭的,而 proto3 枚举是开放的。在 Editions 中,此行为可通过 features.enum_type 特性进行配置。

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

原始值访问器被定义为 C 函数,而不是 Objective-C 方法,因为它们在大多数情况下不被使用。将它们声明为 C 函数可以减少浪费的 Objective-C 运行时信息,并允许链接器可能对其进行死代码剥离(dead strip)。

Swift 枚举支持

苹果在与 C API 交互中记录了他们如何将 Objective-C 枚举导入到 Swift 枚举。Protocol buffer 生成的枚举支持 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

知名类型

如果您使用 protocol buffers 提供的任何消息类型,它们通常只会在生成的 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 和 editions)

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

message Foo {
  extensions 100 to 199;
}

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

message Bar {
  extend Foo {
    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]]);