Objective-C 生成程式碼指南

準確描述協議緩衝區編譯器為任何給定的協議定義生成的 Objective-C 程式碼。

本文件重點介紹了 proto2、proto3 和 Editions 生成程式碼之間的任何差異。在閱讀本文件之前,您應該閱讀proto2 語言指南和/或proto3 語言指南和/或Editions 指南

編譯器呼叫

當使用 --objc_out= 命令列標誌呼叫時,協議緩衝區編譯器會生成 Objective-C 輸出。--objc_out= 選項的引數是您希望編譯器寫入 Objective-C 輸出的目錄。編譯器為每個 .proto 檔案輸入建立一個頭檔案和一個實現檔案。輸出檔案的名稱透過獲取 .proto 檔案的名稱並進行以下更改來計算

  • 檔名透過將 .proto 檔案基本名稱轉換為駝峰命名來確定。例如,foo_bar.proto 將變為 FooBar
  • 副檔名 (.proto) 分別替換為標頭檔案或實現檔案的 pbobjc.hpbobjc.m
  • 協議路徑(透過 --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 檔案的標準已經將訊息命名為駝峰命名。假設使用者出於充分的理由繞過了約定,並且實現將符合他們的意圖。

從欄位名和 oneofs、enum 宣告以及擴充套件訪問器生成的名稱將轉換為駝峰命名。通常,要將 proto 名稱轉換為駝峰命名的 Objective-C 名稱

  • 第一個字母轉換為大寫(欄位除外,欄位總是以小寫字母開頭)。
  • 對於名稱中的每個下劃線,下劃線被移除,其後的字母大寫。

例如,欄位 foo_bar_baz 變為 fooBarBaz。欄位 FOO_bar 變為 fooBar

訊息

給定一個簡單的訊息宣告:

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;

未知欄位

當解析訊息時,它可能包含解析程式碼未知的欄位。當使用 .proto 定義的舊版本建立訊息,然後使用從新版本生成的程式碼解析時(反之亦然),可能會發生這種情況。

這些欄位不會被丟棄,並存儲在訊息的 unknownFields 屬性中

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

您可以使用 GPBUnknownFieldSet 介面按編號獲取這些欄位或將其作為陣列遍歷。

欄位

以下部分描述了協議緩衝區編譯器為訊息欄位生成的程式碼。它們按隱式和顯式存在進行劃分。您可以在欄位存在中瞭解更多關於此區別的資訊。

具有隱式存在性的單一欄位

對於每個單一欄位,編譯器都會生成一個屬性來儲存資料和一個包含欄位編號的整數常量。訊息型別欄位還會獲得一個 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 設定器模式將不起作用。

具有顯式存在性的單一欄位

對於每個單一欄位,編譯器都會生成一個屬性來儲存資料,一個包含欄位編號的整數常量,以及一個 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 設定器模式將不起作用。

重複欄位

與單一欄位(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

注意:重複欄位的行為可以在 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>Arrays (除了 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 檔案編譯。

未識別的列舉值根據語言版本和 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 列舉。協議緩衝區生成的列舉支援 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

知名型別

如果您使用協議緩衝區提供的任何訊息型別,它們通常只會使用其 proto 定義在生成的 Objective-C 程式碼中,但我們在類別中提供了一些基本的轉換方法以使其使用更簡單。請注意,我們尚未為所有知名型別提供特殊的 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)

您可以透過使用 extension_generation_mode 選項控制擴充套件的生成。有三種模式

  • class_based:為擴充套件生成 Objective-C 類和方法(傳統)。
  • c_function:為擴充套件生成 C 函式(新)。
  • migration:兩者都生成。

基於類 (傳統)

給定一個帶有擴充套件範圍的訊息

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

C 函式 (新)

使用 c_function 模式時,將生成 C 函式而不是 Objective-C 類。這減少了二進位制檔案大小並避免了名稱衝突。

生成函式的命名約定涉及 proto 包和副檔名。如果定義了 objc_class_prefix,它將前置。

  • 檔案作用域登錄檔:<Prefix><Package>_<File>_Registry()
  • 檔案作用域擴充套件:<Prefix><Package>_extension_<Field>()
  • 訊息作用域擴充套件:<Prefix><Package>_<ScopeMessage>_extension_<Field>()

(注意:Package 是駝峰命名的 proto 包名。File 是檔名。ScopeMessage 是包含擴充套件定義的訊息。)

使用與上面相同的示例,但假設 package my.package; 和檔案 test2.proto

編譯器在標頭檔案中生成宣告為 C 函式

// Registry function for the file.
GPBExtensionRegistry *MyPackage_Test2_Registry(void);

// File-scoped extensions.
GPBExtensionDescriptor *MyPackage_extension_Foo(void);
GPBExtensionDescriptor *MyPackage_extension_RepeatedFoo(void);

// Message-scoped extensions (scoped to Bar).
GPBExtensionDescriptor *MyPackage_Bar_extension_Bar(void);
GPBExtensionDescriptor *MyPackage_Bar_extension_RepeatedBar(void);

獲取和設定這些擴充套件欄位

Foo *fooMsg = [[Foo alloc] init];

// Set the single field extensions
[fooMsg setExtension:MyPackage_extension_Foo() value:@5];
NSAssert([fooMsg hasExtension:MyPackage_extension_Foo()]);
NSAssert([[fooMsg getExtension:MyPackage_extension_Foo()] intValue] == 5);

// Add two things to the repeated extension:
[fooMsg addExtension:MyPackage_extension_RepeatedFoo() value:@1];
[fooMsg addExtension:MyPackage_extension_RepeatedFoo() value:@2];
NSAssert([fooMsg hasExtension:MyPackage_extension_RepeatedFoo()]);
NSAssert([[fooMsg getExtension:MyPackage_extension_RepeatedFoo()] count] == 2);

// Clearing
[fooMsg clearExtension:MyPackage_extension_Foo()];
[fooMsg clearExtension:MyPackage_extension_RepeatedFoo()];
NSAssert(![fooMsg hasExtension:MyPackage_extension_Foo()]);
NSAssert(![fooMsg hasExtension:MyPackage_extension_RepeatedFoo()]);