Objective-C 生成コードガイド
proto2 と proto3 の生成コードの違いが強調されています。このドキュメントを読む前に、proto2 言語ガイドおよび/またはproto3 言語ガイドを読むことをお勧めします。
コンパイラの呼び出し
Protocol Buffer コンパイラは、`--objc_out=` コマンドラインフラグを指定して呼び出されたときに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` を読み込み、4つの出力ファイル: `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` は作成しません。それらは既に存在している必要があります。
パッケージ
Objective-C は言語による強制的な名前空間を持たないため、Protocol Buffer コンパイラによって生成されるObjective-Cコードは、`.proto` ファイルで定義されたパッケージ名に全く影響されません。代わりに、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` である内部メッセージの場合、生成されたインターフェースは、`FieldNumber` 列挙型または `OneOfCase` 列挙型と生成された名前が衝突しないように、キャメルケース名に `_Class` がサフィックスとして付加されます。
メッセージは他のメッセージの内部に宣言することもできます。
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]` に設定されます。バイトまたは文字列フィールドが設定されているかどうかをテストするには、その `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` セッターパターンは機能しません。
単一フィールド (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]` に設定されます。バイトまたは文字列フィールドが設定されているかどうかをテストするには、その `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` セッターパターンは機能しません。
繰り返しフィールド
単一フィールド (proto2 proto3) と同様に、Protocol Buffer コンパイラは各繰り返しフィールドに対して1つのデータプロパティを生成します。このデータプロパティは、フィールドの型に応じて `GPB
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
Foo *myFoo = [[Foo alloc] init];
[myFoo.stringValueArray addObject:@"A string"]
また、提供されている `
if (myFoo.messageValueArray_Count) {
// There is something in the array...
}
`GPBArray` インターフェース
`GPB
@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 プロパティのいずれか1つを設定すると、その 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` は、クラスの数を減らすため、またObjective-Cが `NSMutableDictionary` と連携する方法と一致させるために、`string` `bytes` または `message` 型の値に使用されます。
デフォルト値
マップフィールドのデフォルト値は空です。Objective-C の生成コードでは、これは空の `GBP
また、提供されている `
if (myFoo.myMap_Count) {
// There is something in the map...
}
`GBPDictionary` インターフェース
`GBP
@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
@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
@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 Buffersのバージョンによって異なる扱いを受けます。proto3 では、解析されたメッセージデータ内の列挙値が、それを読み込むコードがサポートするようにコンパイルされたものでない場合、型付けされた列挙値に対して `kGPBUnrecognizedEnumeratorValue` が返されます。実際の値が必要な場合は、生のアクセサーを使用して値を `int32_t` として取得します。proto2 を使用している場合、認識されない列挙値は不明なフィールドとして扱われます。
`kGPBUnrecognizedEnumeratorValue` は `0xFBADBEEF` として定義されており、列挙型内のいずれかの列挙子にこの値がある場合、エラーとなります。任意の列挙フィールドをこの値に設定しようとすると、実行時エラーになります。同様に、型付けされたアクセサーを使用して、その列挙型で定義されていない列挙子に任意の列挙フィールドを設定しようとすると、実行時エラーになります。どちらのエラーケースでも、デバッグビルドではアサートが発生し、リリースビルドではログを記録してフィールドをデフォルト値 (`0`) に設定します。
生のアクセサーは、ほとんどの場合使用されないため、Objective-C メソッドではなく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
Well-Known 型 (proto3 のみ)
proto3 で提供されるメッセージ型のいずれかを使用する場合、それらは一般的に生成されたObjective-Cコードでそのままプロト定義を使用しますが、より簡単に使用できるように、基本的な変換メソッドをカテゴリで提供しています。ただし、`Any` を含むすべてのWell-Known 型に対して、まだ特別な API はありません (現在、`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]]);