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.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.proto 和 src/bar/baz.proto 文件,并生成四个输出文件:build/gen/Foo.pbobjc.h、build/gen/Foo.pbobjc.m、build/gen/bar/Baz.pbobjc.h 和 build/gen/bar/Baz.pbobjc.m。编译器会自动创建目录 build/gen/bar(如果需要),但它*不会*创建 build 或 build/gen;它们必须已经存在。
包(Packages)
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 文件的标准已经是使用驼峰式命名法来命名消息。我们假设用户有充分的理由绕过这个约定,实现将符合他们的意图。
从字段名和 oneof、enum 声明以及扩展访问器生成的方法,其名称将被转换为驼峰式命名法。通常,要从 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 *不会*被转换。如果内部消息的驼峰式名称是 FieldNumber 或 OneOfCase,生成的接口将是驼峰式名称加上 _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 返回 nil,那么 foo.a.b 的 setter 模式将无法工作。
具有显式存在性的单一字段
对于每个单一字段,编译器会生成一个用于存储数据的属性、一个包含字段编号的整数常量,以及一个 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 返回 nil,那么 foo.a.b 的 setter 模式将无法工作。
重复字段
与单一字段(proto2 proto3)类似,protocol buffer 编译器为每个重复字段生成一个数据属性。这个数据属性是一个 GPB<VALUE>Array,具体类型取决于字段类型,其中 <VALUE> 可以是 UInt32、Int32、UInt64、Int64、Bool、Float、Double 或 Enum。对于 string、bytes 和 message 类型,将使用 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用于类型为string、bytes或message的值,以减少类的数量,并与 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>ObjectDictionary 和 GBP<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 运行时信息,并允许链接器可能对其进行死代码剥离。
Swift 枚举支持
Apple 在与 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 buffer 提供的任何消息类型,它们通常只会在生成的 Objective-C 代码中使用其 proto 定义,不过我们会在类别 (categories) 中提供一些基本的转换方法,以简化它们的使用。请注意,我们尚未为所有知名类型提供特殊的 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
Duration
@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]]);