diff --git a/objectivec/GPBUnknownFields.h b/objectivec/GPBUnknownFields.h index 07a7af5f51..859614b645 100644 --- a/objectivec/GPBUnknownFields.h +++ b/objectivec/GPBUnknownFields.h @@ -109,6 +109,23 @@ __attribute__((objc_subclassing_restricted)) **/ - (nonnull GPBUnknownFields *)addGroupWithFieldNumber:(int32_t)fieldNumber; +/** + * Add the copy of the given unknown field. + * + * This can be useful from processing one `GPBUnknownFields` to create another. + * + * NOTE: If the field being copied is an Group, this instance added is new and thus + * the `.group` of that result is also new, so if you intent is to modify the group + * it *must* be fetched out of the result. + * + * It is a programming error to call this when the `type` is a legacy field. + * + * @param field The field to add. + * + * @return The autoreleased field that was added. + **/ +- (GPBUnknownField *)addCopyOfField:(nonnull GPBUnknownField *)field; + /** * Removes the given field from the set. * diff --git a/objectivec/GPBUnknownFields.m b/objectivec/GPBUnknownFields.m index 890be4286e..dc004b3fb8 100644 --- a/objectivec/GPBUnknownFields.m +++ b/objectivec/GPBUnknownFields.m @@ -323,6 +323,16 @@ static BOOL MergeFromInputStream(GPBUnknownFields *self, GPBCodedInputStream *in return [group autorelease]; } +- (GPBUnknownField *)addCopyOfField:(nonnull GPBUnknownField *)field { + if (field->type_ == GPBUnknownFieldTypeLegacy) { + [NSException raise:NSInternalInconsistencyException + format:@"GPBUnknownField is the wrong type"]; + } + GPBUnknownField *result = [field copy]; + [fields_ addObject:result]; + return [result autorelease]; +} + - (void)removeField:(nonnull GPBUnknownField *)field { NSUInteger count = fields_.count; [fields_ removeObjectIdenticalTo:field]; diff --git a/objectivec/Tests/GPBUnknownFieldsTest.m b/objectivec/Tests/GPBUnknownFieldsTest.m index c73e0649c1..2b8a085072 100644 --- a/objectivec/Tests/GPBUnknownFieldsTest.m +++ b/objectivec/Tests/GPBUnknownFieldsTest.m @@ -662,6 +662,37 @@ XCTAssertEqual(loop, 10); } +- (void)testAddCopyOfField { + GPBUnknownFields* ufs = [[[GPBUnknownFields alloc] init] autorelease]; + [ufs addFieldNumber:1 varint:10]; + [ufs addFieldNumber:2 fixed32:11]; + [ufs addFieldNumber:3 fixed64:12]; + [ufs addFieldNumber:4 lengthDelimited:DataFromCStr("foo")]; + GPBUnknownFields* group = [ufs addGroupWithFieldNumber:5]; + [group addFieldNumber:10 varint:100]; + GPBUnknownFields* subGroup = [group addGroupWithFieldNumber:100]; + [subGroup addFieldNumber:50 varint:50]; + + GPBUnknownFields* ufs2 = [[[GPBUnknownFields alloc] init] autorelease]; + for (GPBUnknownField* field in ufs) { + GPBUnknownField* field2 = [ufs2 addCopyOfField:field]; + XCTAssertEqualObjects(field, field2); + if (field.type == GPBUnknownFieldTypeGroup) { + // Group does a copy because the `.group` value is mutable. + XCTAssertTrue(field != field2); // Pointer comparison. + XCTAssertTrue(group != field2.group); // Pointer comparison. + XCTAssertEqualObjects(group, field2.group); + GPBUnknownFields* subGroupAdded = [field2.group firstGroup:100]; + XCTAssertTrue(subGroupAdded != subGroup); // Pointer comparison. + XCTAssertEqualObjects(subGroupAdded, subGroup); + } else { + // All other types are immutable, so they use the same object. + XCTAssertTrue(field == field2); // Pointer comparision. + } + } + XCTAssertEqualObjects(ufs, ufs2); +} + - (void)testDescriptions { // Exercise description for completeness. GPBUnknownFields* ufs = [[[GPBUnknownFields alloc] init] autorelease];