Objective-C 產生程式碼指南

說明 protocol buffer 編譯器為任何給定的協定定義所產生的 Objective-C 程式碼。

proto2 和 proto3 產生程式碼之間的任何差異都會特別標示。在閱讀本文件之前,您應該先閱讀 proto2 語言指南 和/或 proto3 語言指南

編譯器調用

當使用 --objc_out= 命令列旗標調用時,protocol buffer 編譯器會產生 Objective-C 輸出。--objc_out= 選項的參數是您希望編譯器寫入 Objective-C 輸出的目錄。編譯器會為每個 .proto 檔案輸入建立標頭檔和實作檔案。輸出檔案的名稱是透過取得 .proto 檔案的名稱並進行以下變更來計算

  • 檔案名稱由將 .proto 檔案基本名稱轉換為駝峰式大小寫來決定。例如,foo_bar.proto 將變成 FooBar
  • 副檔名 (.proto) 會分別針對標頭檔或實作檔案替換為 pbobjc.hpbobjc.m
  • proto 路徑 (使用 --proto_path=-I 命令列旗標指定) 會替換為輸出路徑 (使用 --objc_out= 旗標指定)。

因此,舉例來說,如果您如下調用編譯器

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

編譯器將讀取檔案 src/foo.protosrc/bar/baz.proto,並產生四個輸出檔案:build/gen/Foo.pbobjc.hbuild/gen/Foo.pbobjc.mbuild/gen/bar/Baz.pbobjc.hbuild/gen/bar/Baz.pbobjc.m。如果需要,編譯器將自動建立目錄 build/gen/bar,但不會建立 buildbuild/gen;它們必須已存在。

套件

protocol buffer 編譯器產生的 Objective-C 程式碼完全不受 .proto 檔案中定義的套件名稱影響,因為 Objective-C 沒有語言強制的命名空間。相反地,Objective-C 類別名稱使用前綴來區分,您可以在下一節中找到相關資訊。

類別前綴

假設有以下 檔案選項

option objc_class_prefix = "CGOOP";

指定的字串 (在本例中為 CGOOP) 會加在為此 .proto 檔案產生的所有 Objective-C 類別前面。使用 Apple 建議的 3 個或更多字元的字首。請注意,所有 2 個字母的字首均由 Apple 保留。

駝峰式大小寫轉換

慣用的 Objective-C 對所有識別碼都使用駝峰式大小寫。

訊息的名稱不會轉換,因為 proto 檔案的標準已經是以駝峰式大小寫命名訊息。假設使用者基於充分的理由繞過了慣例,並且實作將符合他們的意圖。

從欄位名稱和 oneofenum 宣告和擴充存取器產生的方法將採用駝峰式大小寫命名。一般來說,若要將 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;

不明欄位 (僅限 proto2)

如果使用 較舊版本 .proto 定義建立的訊息使用從較新版本 (或反之亦然) 產生的程式碼進行剖析,則該訊息可能包含「新」程式碼無法辨識的選用或重複欄位。在 proto2 產生的程式碼中,這些欄位不會捨棄,而是儲存在訊息的 unknownFields 屬性中。

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

您可以使用 GPBUnknownFieldSet 介面依編號擷取這些欄位,或將它們作為陣列迴圈。

在 proto3 中,不明欄位會在剖析訊息時直接捨棄。

欄位

以下章節說明 protocol buffer 編譯器為訊息欄位產生的程式碼。

單數欄位 (proto3)

對於每個單數欄位,編譯器都會產生一個屬性來儲存資料,以及一個包含欄位編號的整數常數。訊息類型欄位也會取得 has.. 屬性,讓您檢查欄位是否在編碼訊息中設定。因此,舉例來說,假設有以下訊息

message Foo {
  message Bar {
    int32 int32_value = 1;
  }
  enum Qux {...}
  int32 int32_value = 1;
  string string_value = 2;
  Bar message_value = 3;
  Qux enum_value = 4;
  bytes bytes_value = 5;
}

編譯器將產生以下內容

typedef GPB_ENUM(Foo_Bar_FieldNumber) {
  // The generated field number name is the enclosing message names delimited by
  // underscores followed by "FieldNumber", followed by the field name
  // camel cased.
  Foo_Bar_FieldNumber_Int32Value = 1,
};

@interface Foo_Bar : GPBMessage
@property(nonatomic, readwrite) int32_t int32Value;
@end

typedef GPB_ENUM(Foo_FieldNumber) {
  Foo_FieldNumber_Int32Value = 1,
  Foo_FieldNumber_StringValue = 2,
  Foo_FieldNumber_MessageValue = 3,
  Foo_FieldNumber_EnumValue = 4,
  Foo_FieldNumber_BytesValue = 5,
};

typedef GPB_ENUM(Foo_Qux) {
  Foo_Qux_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
  ...
};

@interface Foo : GPBMessage
// Field names are camel cased.
@property(nonatomic, readwrite) int32_t int32Value;
@property(nonatomic, readwrite, copy, null_resettable) NSString *stringValue;
@property(nonatomic, readwrite) BOOL hasMessageValue;
@property(nonatomic, readwrite, strong, null_resettable) Foo_Bar *messageValue;
@property(nonatomic, readwrite) Foo_Qux enumValue;
@property(nonatomic, readwrite, copy, null_resettable) NSData *bytesValue;
@end

特殊命名案例

在某些情況下,欄位名稱產生規則可能會導致名稱衝突,並且名稱需要「唯一化」。此類衝突會透過在欄位結尾附加 _p 來解決 (選擇 _p 是因為它相當獨特,並且代表「屬性」)。

message Foo {
  int32 foo_array = 1;      // Ends with Array
  int32 bar_OneOfCase = 2;  // Ends with oneofcase
  int32 id = 3;             // Is a C/C++/Objective-C keyword
}

產生

typedef GPB_ENUM(Foo_FieldNumber) {
  // If a non-repeatable field name ends with "Array" it will be suffixed
  // with "_p" to keep the name distinct from repeated types.
  Foo_FieldNumber_FooArray_p = 1,
  // If a field name ends with "OneOfCase" it will be suffixed with "_p" to
  // keep the name distinct from OneOfCase properties.
  Foo_FieldNumber_BarOneOfCase_p = 2,
  // If a field name is a C/C++/ObjectiveC keyword it will be suffixed with
  // "_p" to allow it to compile.
  Foo_FieldNumber_Id_p = 3,
};

@interface Foo : GPBMessage
@property(nonatomic, readwrite) int32_t fooArray_p;
@property(nonatomic, readwrite) int32_t barOneOfCase_p;
@property(nonatomic, readwrite) int32_t id_p;
@end

預設值

數值類型的 預設值0

字串的預設值為 @"",位元組的預設值為 [NSData data]

nil 指派給字串欄位會在偵錯中斷言,並在發行版本中將欄位設定為 @""。將 nil 指派給位元組欄位會在偵錯中斷言,並在發行版本中將欄位設定為 [NSData data]。若要測試位元組或字串欄位是否已設定,需要測試其長度屬性並將其與 0 比較。

訊息的預設「空值」是預設訊息的執行個體。若要清除訊息值,應將其設定為 nil。存取已清除的訊息將會傳回預設訊息的執行個體,且 hasFoo 方法會傳回 false。

為欄位傳回的預設訊息是本機執行個體。傳回預設訊息而不是 nil 的原因是,在以下情況中

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

實作將支援

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

其中 a 將在必要時透過存取器自動建立。如果 foo.a 傳回 nil,則 foo.a.b setter 模式將無法運作。

單數欄位 (proto2)

對於每個單數欄位,編譯器都會產生一個屬性來儲存資料、一個包含欄位編號的整數常數,以及一個 has.. 屬性,讓您檢查欄位是否在編碼訊息中設定。因此,舉例來說,假設有以下訊息

message Foo {
  message Bar {
    int32 int32_value = 1;
  }
  enum Qux {...}
  optional int32 int32_value = 1;
  optional string string_value = 2;
  optional Bar message_value = 3;
  optional Qux enum_value = 4;
  optional bytes bytes_value = 5;
}

編譯器將產生以下內容

# Enum Foo_Qux

typedef GPB_ENUM(Foo_Qux) {
  Foo_Qux_Flupple = 0,
};

GPBEnumDescriptor *Foo_Qux_EnumDescriptor(void);

BOOL Foo_Qux_IsValidValue(int32_t value);

# Message Foo

typedef GPB_ENUM(Foo_FieldNumber) {
  Foo_FieldNumber_Int32Value = 2,
  Foo_FieldNumber_MessageValue = 3,
  Foo_FieldNumber_EnumValue = 4,
  Foo_FieldNumber_BytesValue = 5,
  Foo_FieldNumber_StringValue = 6,
};

@interface Foo : GPBMessage

@property(nonatomic, readwrite) BOOL hasInt32Value;
@property(nonatomic, readwrite) int32_t int32Value;

@property(nonatomic, readwrite) BOOL hasStringValue;
@property(nonatomic, readwrite, copy, null_resettable) NSString *stringValue;

@property(nonatomic, readwrite) BOOL hasMessageValue;
@property(nonatomic, readwrite, strong, null_resettable) Foo_Bar *messageValue;

@property(nonatomic, readwrite) BOOL hasEnumValue;
@property(nonatomic, readwrite) Foo_Qux enumValue;

@property(nonatomic, readwrite) BOOL hasBytesValue;
@property(nonatomic, readwrite, copy, null_resettable) NSData *bytesValue;

@end

# Message Foo_Bar

typedef GPB_ENUM(Foo_Bar_FieldNumber) {
  Foo_Bar_FieldNumber_Int32Value = 1,
};

@interface Foo_Bar : GPBMessage

@property(nonatomic, readwrite) BOOL hasInt32Value;
@property(nonatomic, readwrite) int32_t int32Value;

@end

特殊命名案例

在某些情況下,欄位名稱產生規則可能會導致名稱衝突,並且名稱需要「唯一化」。此類衝突會透過在欄位結尾附加 _p 來解決 (選擇 _p 是因為它相當獨特,並且代表「屬性」)。

message Foo {
  optional int32 foo_array = 1;      // Ends with Array
  optional int32 bar_OneOfCase = 2;  // Ends with oneofcase
  optional int32 id = 3;             // Is a C/C++/Objective-C keyword
}

產生

typedef GPB_ENUM(Foo_FieldNumber) {
  // If a non-repeatable field name ends with "Array" it will be suffixed
  // with "_p" to keep the name distinct from repeated types.
  Foo_FieldNumber_FooArray_p = 1,
  // If a field name ends with "OneOfCase" it will be suffixed with "_p" to
  // keep the name distinct from OneOfCase properties.
  Foo_FieldNumber_BarOneOfCase_p = 2,
  // If a field name is a C/C++/ObjectiveC keyword it will be suffixed with
  // "_p" to allow it to compile.
  Foo_FieldNumber_Id_p = 3,
};

@interface Foo : GPBMessage
@property(nonatomic, readwrite) int32_t fooArray_p;
@property(nonatomic, readwrite) int32_t barOneOfCase_p;
@property(nonatomic, readwrite) int32_t id_p;
@end

預設值 (僅限選用欄位)

數值類型的 預設值 (如果使用者未明確指定預設值) 為 0

字串的預設值為 @"",位元組的預設值為 [NSData data]

nil 指派給字串欄位會在偵錯中斷言,並在發行版本中將欄位設定為 @""。將 nil 指派給位元組欄位會在偵錯中斷言,並在發行版本中將欄位設定為 [NSData data]。若要測試位元組或字串欄位是否已設定,需要測試其長度屬性並將其與 0 比較。

訊息的預設「空值」是預設訊息的執行個體。若要清除訊息值,應將其設定為 nil。存取已清除的訊息將會傳回預設訊息的執行個體,且 hasFoo 方法會傳回 false。

為欄位傳回的預設訊息是本機執行個體。傳回預設訊息而不是 nil 的原因是,在以下情況中

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

實作將支援

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

其中 a 將在必要時透過存取器自動建立。如果 foo.a 傳回 nil,則 foo.a.b setter 模式將無法運作。

重複欄位

與單數欄位 (proto2 proto3) 類似,protocol buffer 編譯器會為每個重複欄位產生一個資料屬性。此資料屬性為 GPB<VALUE>Array,具體取決於欄位類型,其中 <VALUE> 可以是 UInt32Int32UInt64Int64BoolFloatDoubleEnum 的其中之一。NSMutableArray 將用於 stringbytesmessage 類型。重複類型的欄位名稱會附加 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;
};

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 中的任何欄位。

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 或字串。
  • <VALUE> 為 UInt32、Int32、UInt64、Int64、Bool、Float、Double、Enum 或 Object。Object 用於 string bytesmessage 類型的值,以減少類別數量,並符合 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 檔案編譯的。

未辨識的列舉值會根據您使用的 protocol buffers 版本而有不同的處理方式。在 proto3 中,如果剖析訊息資料中的列舉值不是程式碼讀取器編譯以支援的值,則會針對類型化列舉器值傳回 kGPBUnrecognizedEnumeratorValue。如果需要實際值,請使用原始值存取器以取得值作為 int32_t。如果您使用 proto2,則未辨識的列舉值會視為不明欄位。

kGPBUnrecognizedEnumeratorValue 定義為 0xFBADBEEF,如果列舉中的任何列舉器具有此值,則會發生錯誤。嘗試將任何列舉欄位設定為此值是執行階段錯誤。同樣地,嘗試使用類型化存取器將任何列舉欄位設定為未由其列舉類型定義的列舉器也是執行階段錯誤。在這兩種錯誤情況下,偵錯組建都會導致斷言,而發行組建將會記錄並將欄位設定為其預設值 (0)。

原始值存取器定義為 C 函式,而不是 Objective-C 方法,因為在大多數情況下不會使用它們。將它們宣告為 C 函式可減少浪費的 Objective-C 執行階段資訊,並允許連結器可能將它們進行 dead strip。

Swift 列舉支援

Apple 文件說明了他們如何將 Objective-C 列舉匯入 Swift 列舉,請參閱 與 C API 互動。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

廣為人知的類型 (僅限 proto3)

如果您使用 proto3 提供的任何訊息類型,它們通常只會在產生的 Objective-C 程式碼中使用其 proto 定義,但我們在類別中提供了一些基本轉換方法,以簡化它們的使用。請注意,我們目前尚未針對所有廣為人知的類型提供特殊 API,包括 Any (目前沒有協助程式方法可將 Any 的訊息值轉換為適當類型的訊息)。

時間戳記

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

持續時間

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

擴充 (僅限 proto2)

假設訊息具有 擴充範圍

message Foo {
  extensions 100 to 199;
}

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

message Bar {
  extend Foo {
    optional int32 bar = 103;
    repeated int32 repeated_bar = 104;
  }
}

編譯器會產生以下內容

# File Test2Root

@interface Test2Root : GPBRootObject

// The base class provides:
//   + (GPBExtensionRegistry *)extensionRegistry;
// which is an GPBExtensionRegistry that includes all the extensions defined by
// this file and all files that it depends on.

@end

@interface Test2Root (DynamicMethods)
+ (GPBExtensionDescriptor *)foo;
+ (GPBExtensionDescriptor *)repeatedFoo;
@end

# Message Foo

@interface Foo : GPBMessage

@end

# Message Bar

@interface Bar : GPBMessage

@end

@interface Bar (DynamicMethods)

+ (GPBExtensionDescriptor *)bar;
+ (GPBExtensionDescriptor *)repeatedBar;
@end

若要取得和設定這些擴充欄位,您可以使用以下內容

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

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

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

// Clearing
[fooMsg clearExtension:[Test2Root foo]];
[fooMsg clearExtension:[Test2Root repeatedFoo]];
NSAssert(![fooMsg hasExtension:[Test2Root foo]]);
NSAssert(![fooMsg hasExtension:[Test2Root repeatedFoo]]);