Objective-C 程式碼產生指南

詳細說明協定緩衝區編譯器針對任何給定的協定定義所產生的 Objective-C 程式碼。

proto2 和 proto3 產生的程式碼之間的任何差異都會加以強調。在閱讀本文檔之前,您應該先閱讀proto2 語言指南和/或proto3 語言指南

編譯器調用

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

  • 檔案名稱是透過將 .proto 檔案的基本名稱轉換為駝峰式大小寫來決定的。例如,foo_bar.proto 會變成 FooBar
  • 副檔名 (.proto) 會分別以 pbobjc.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;它們必須已經存在。

套件

協定緩衝區編譯器產生的 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 {}

協定緩衝區編譯器會產生一個名為 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 定義產生的程式碼來剖析使用舊版本的 .proto 定義建立的訊息(反之亦然),則該訊息可能包含「新」程式碼無法辨識的選用或重複欄位。在 proto2 產生的程式碼中,這些欄位不會捨棄,而是儲存在訊息的 unknownFields 屬性中。

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

您可以使用 GPBUnknownFieldSet 介面依數字擷取這些欄位,或以陣列形式循環處理它們。

在 proto3 中,當剖析訊息時,未知欄位會直接捨棄。

欄位

下列章節說明協定緩衝區編譯器針對訊息欄位產生的程式碼。

單數欄位(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 或 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 運行時資訊量,並可能允許連結器進行死碼消除。

如果外部列舉的名稱與任何 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 buffer 版本而有不同的處理方式。在 proto3 中,如果已剖析訊息資料中的列舉元值不是程式碼編譯時支援的值,則會為型別化的列舉元值傳回 kGPBUnrecognizedEnumeratorValue。如果需要實際值,請使用原始值存取器來取得值作為 int32_t。如果您使用 proto2,未識別的列舉值會被視為未知欄位。

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

眾所周知的類型(僅限 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]]);