Objective-C で生成されたコードのガイド

プロトコルバッファコンパイラが、与えられたプロトコル定義に対してどのような Objective-C コードを生成するかを正確に記述します。

proto2 と proto3 で生成されるコードの違いはすべて強調表示されています。このドキュメントを読む前に、proto2 言語ガイド および/または proto3 言語ガイド を読む必要があります。

コンパイラの起動

プロトコルバッファコンパイラは、--objc_out= コマンドラインフラグを指定して起動すると、Objective-C 出力を生成します。--objc_out= オプションのパラメータは、コンパイラに Objective-C 出力を書き込ませたいディレクトリです。コンパイラは、入力された .proto ファイルごとにヘッダーファイルと実装ファイルを作成します。出力ファイルの名前は、.proto ファイルの名前を取得し、次の変更を加えることによって計算されます。

  • ファイル名は、.proto ファイルのベース名をキャメルケースに変換することによって決定されます。たとえば、foo_bar.protoFooBar になります。
  • 拡張子 (.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.hbuild/gen/Foo.pbobjc.mbuild/gen/bar/Baz.pbobjc.h、および build/gen/bar/Baz.pbobjc.m) を生成します。コンパイラは、必要に応じて build/gen/bar ディレクトリを自動的に作成しますが、build または build/gen は作成しません。これらはすでに存在している必要があります。

パッケージ

Objective-C には言語で強制される名前空間がないため、プロトコルバッファコンパイラによって生成された Objective-C コードは、.proto ファイルで定義されたパッケージ名の影響をまったく受けません。代わりに、Objective-C クラス名はプレフィックスを使用して区別されます。これについては、次のセクションで説明します。

クラスプレフィックス

次の ファイルオプション が与えられた場合:

option objc_class_prefix = "CGOOP";

指定された文字列 (この場合は CGOOP) は、この .proto ファイルに対して生成されたすべての Objective-C クラスの先頭にプレフィックスとして付加されます。Apple が推奨するように、3 文字以上のプレフィックスを使用してください。2 文字のプレフィックスはすべて Apple によって予約されていることに注意してください。

キャメルケース変換

慣用的な Objective-C では、すべての識別子にキャメルケースを使用します。

メッセージ名はすでにキャメルケースで命名するのが proto ファイルの標準であるため、変換されません。ユーザーが正当な理由で慣例をバイパスしたと見なされ、実装はユーザーの意図に従います。

フィールド名および oneofenum 宣言、および拡張機能アクセサから生成されたメソッドは、キャメルケースになります。一般に、proto 名からキャメルケースの Objective-C 名に変換するには:

  • 最初の文字を大文字に変換します (フィールドは常に小文字で始まる場合を除く)。
  • 名前の各アンダースコアについて、アンダースコアを削除し、後続の文字を大文字にします。

たとえば、フィールド foo_bar_bazfooBarBaz になります。フィールド FOO_barfooBar になります。

メッセージ

シンプルなメッセージ宣言が与えられた場合:

message Foo {}

プロトコルバッファコンパイラは、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 では、不明なフィールドはメッセージが解析されると単純に破棄されます。

フィールド

次のセクションでは、メッセージフィールドに対してプロトコルバッファコンパイラによって生成されるコードについて説明します。

単数フィールド (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.anil を返した場合、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.anil を返した場合、foo.a.b セッターパターンは機能しません。

繰り返しフィールド

単数フィールド(proto2 proto3) と同様に、プロトコルバッファコンパイラは、繰り返しフィールドごとに 1 つのデータプロパティを生成します。このデータプロパティは、フィールド型に応じて GPB<VALUE>Array です。ここで、<VALUE> は、UInt32Int32UInt64Int64BoolFloatDouble、または Enum のいずれかになります。NSMutableArray は、stringbytes、および message 型に使用されます。繰り返し型のフィールド名には、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

string、bytes、および message フィールドの場合、配列の要素はそれぞれ 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;
};

プロトコルバッファコンパイラは次を生成します:

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 は、クラスの数を減らすために string bytes または message 型の値に使用され、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>ObjectDictionary および GBP<KEY>EnumDictionary を除く) インターフェースは次のとおりです:

@interface GPB<KEY>Dictionary : NSObject
@property (nonatomic, readonly) NSUInteger count;

+ (instancetype)dictionary;
+ (instancetype)dictionaryWithValue:(const )value
                             forKey:(const <KEY>)key;
+ (instancetype)dictionaryWithValues:(const  [])values
                             forKeys:(const <KEY> [])keys
                               count:(NSUInteger)count;
+ (instancetype)dictionaryWithDictionary:(GPB<KEY>Dictionary *)dictionary;
+ (instancetype)dictionaryWithCapacity:(NSUInteger)numItems;

- (instancetype)initWithValues:(const  [])values
                       forKeys:(const <KEY> [])keys
                         count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDictionary:(GPB<KEY>Dictionary *)dictionary;
- (instancetype)initWithCapacity:(NSUInteger)numItems;

- (BOOL)valueForKey:(<KEY>)key value:(VALUE *)value;

- (void)enumerateKeysAndValuesUsingBlock:
    (void (^)(<KEY> key,  value, BOOL *stop))block;

- (void)removeValueForKey:(<KEY>)aKey;
- (void)removeAll;
- (void)setValue:()value forKey:(<KEY>)key;
- (void)addEntriesFromDictionary:(GPB<KEY>Dictionary *)otherDictionary;
@end

GBP<KEY>ObjectDictionary インターフェースは次のとおりです:

@interface GPB<KEY>ObjectDictionary : NSObject
@property (nonatomic, readonly) NSUInteger count;

+ (instancetype)dictionary;
+ (instancetype)dictionaryWithObject:(id)object
                             forKey:(const <KEY>)key;
+ (instancetype)
  dictionaryWithObjects:(const id GPB_UNSAFE_UNRETAINED [])objects
                forKeys:(const <KEY> [])keys
                  count:(NSUInteger)count;
+ (instancetype)dictionaryWithDictionary:(GPB<KEY>ObjectDictionary *)dictionary;
+ (instancetype)dictionaryWithCapacity:(NSUInteger)numItems;

- (instancetype)initWithObjects:(const id GPB_UNSAFE_UNRETAINED [])objects
                        forKeys:(const <KEY> [])keys
                          count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDictionary:(GPB<KEY>ObjectDictionary *)dictionary;
- (instancetype)initWithCapacity:(NSUInteger)numItems;

- (id)objectForKey:(uint32_t)key;

- (void)enumerateKeysAndObjectsUsingBlock:
    (void (^)(<KEY> key, id object, BOOL *stop))block;

- (void)removeObjectForKey:(<KEY>)aKey;
- (void)removeAll;
- (void)setObject:(id)object forKey:(<KEY>)key;
- (void)addEntriesFromDictionary:(GPB<KEY>ObjectDictionary *)otherDictionary;
@end

GBP<KEY>EnumDictionary には、検証関数を処理し、生の値をアクセスするためのわずかに異なるインターフェースがあります。

@interface GPB<KEY>EnumDictionary : NSObject

@property(nonatomic, readonly) NSUInteger count;
@property(nonatomic, readonly) GPBEnumValidationFunc validationFunc;

+ (instancetype)dictionary;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                        rawValue:(int32_t)rawValue
                                          forKey:(<KEY>_t)key;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                       rawValues:(const int32_t [])values
                                         forKeys:(const <KEY>_t [])keys
                                           count:(NSUInteger)count;
+ (instancetype)dictionaryWithDictionary:(GPB<KEY>EnumDictionary *)dictionary;
+ (instancetype)dictionaryWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                        capacity:(NSUInteger)numItems;

- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func;
- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                 rawValues:(const int32_t [])values
                                   forKeys:(const <KEY>_t [])keys
                                     count:(NSUInteger)count NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDictionary:(GPB<KEY>EnumDictionary *)dictionary;
- (instancetype)initWithValidationFunction:(nullable GPBEnumValidationFunc)func
                                  capacity:(NSUInteger)numItems;

// These will return kGPBUnrecognizedEnumeratorValue if the value for the key
// is not a valid enumerator as defined by validationFunc. If the actual value is
// desired, use "raw" version of the method.

- (BOOL)valueForKey:(<KEY>_t)key value:(nullable int32_t *)value;

- (void)enumerateKeysAndValuesUsingBlock:
    (void (^)(<KEY>_t key, int32_t value, BOOL *stop))block;

// These methods bypass the validationFunc to provide access to values that were not
// known at the time the binary was compiled.

- (BOOL)valueForKey:(<KEY>_t)key rawValue:(nullable int32_t *)rawValue;

- (void)enumerateKeysAndRawValuesUsingBlock:
    (void (^)(<KEY>_t key, int32_t rawValue, BOOL *stop))block;

- (void)addRawEntriesFromDictionary:(GPB<KEY>EnumDictionary *)otherDictionary;

// If value is not a valid enumerator as defined by validationFunc, these
// methods will assert in debug, and will log in release and assign the value
// to the default value. Use the rawValue methods below to assign non enumerator
// values.

- (void)setValue:(int32_t)value forKey:(<KEY>_t)key;

// This method bypass the validationFunc to provide setting of values that were not
// known at the time the binary was compiled.
- (void)setRawValue:(int32_t)rawValue forKey:(<KEY>_t)key;

// No validation applies to these methods.

- (void)removeValueForKey:(<KEY>_t)aKey;
- (void)removeAll;

@end

列挙型

次のような enum 定義が与えられた場合:

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 の場合:

enum method {}

生成されたインターフェースには、次のように _Enum が接尾辞として付加されます:

// The generated enumeration name is the keyword suffixed by _Enum.
typedef GPB_ENUM(Method_Enum) {}

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 ファイルでコンパイルされているために、サーバーがクライアントが認識できない値を返す場合をサポートするためです。

認識されない enum 値は、使用しているプロトコルバッファのバージョンによって異なる方法で処理されます。proto3 では、解析されたメッセージデータの列挙型値が、それを読み取るコードがコンパイルされたときにサポートするようにコンパイルされていない場合、型付き列挙子値に対して kGPBUnrecognizedEnumeratorValue が返されます。実際の値が必要な場合は、生の値アクセサを使用して値を int32_t として取得します。proto2 を使用している場合、認識されない enum 値は不明なフィールドとして扱われます。

kGPBUnrecognizedEnumeratorValue0xFBADBEEF として定義されており、列挙型の列挙子がこの値を持つ場合はエラーになります。列挙型フィールドをこの値に設定しようとすると、ランタイムエラーが発生します。同様に、型付きアクセサを使用して、列挙型タイプで定義されていない列挙子に列挙型フィールドを設定しようとすると、ランタイムエラーが発生します。どちらのエラーの場合も、デバッグビルドではアサートが発生し、リリースビルドではログが記録され、フィールドがデフォルト値 (0) に設定されます。

生の値アクセサは、ほとんどの場合使用されないため、Objective-C メソッドではなく C 関数として定義されています。C 関数として宣言すると、無駄な Objective-C ランタイム情報が削減され、リンカーがそれらをデッドストリップできる可能性があります。

Swift 列挙型サポート

Apple は、Objective-C 列挙型を Swift 列挙型にインポートする方法を C API との相互作用 でドキュメント化しています。プロトコルバッファで生成された列挙型は、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 types (proto3 のみ)

proto3 で提供されるメッセージ型を使用する場合、一般的に生成された Objective-C コードでは proto 定義がそのまま使用されますが、それらをより簡単に使用できるようにするために、カテゴリにいくつかの基本的な変換メソッドを提供しています。Any (Any のメッセージ値を適切な型のメッセージに変換するヘルパーメソッドは現在ありません) を含む、すべての Well-known types 用の特別な API はまだ用意されていないことに注意してください。

タイムスタンプ

@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 のみ)

拡張機能の範囲 を持つメッセージが与えられた場合:

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