[ObjC] Add api to add a field to another collection of unknown fields.

PiperOrigin-RevId: 663424215
diff --git a/objectivec/GPBUnknownFields.h b/objectivec/GPBUnknownFields.h
index 07a7af5..859614b 100644
--- a/objectivec/GPBUnknownFields.h
+++ b/objectivec/GPBUnknownFields.h
@@ -110,6 +110,23 @@
 - (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.
  *
  * It is a programming error to attempt to remove a field that is not in this collection.
diff --git a/objectivec/GPBUnknownFields.m b/objectivec/GPBUnknownFields.m
index 890be42..dc004b3 100644
--- a/objectivec/GPBUnknownFields.m
+++ b/objectivec/GPBUnknownFields.m
@@ -323,6 +323,16 @@
   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 c73e064..2b8a085 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];