[ObjC] Keep `GPBUnknownField` simpler.

Move some of the new logic out of `GPBUnknownField` so it will end up as a much
simpler "container", with all the serialization logic inside `GPBUnknownFields`
instead.

Also move some of the internal logic needed into static C functions to reduce the
ObjC class overhead of `GPBUnknownFields`.

This was all inspired by realizing during serialization related apis the `type` of
each field was being examined multiple times and reduces that in addition to reducing
the number of methods being invoked.

PiperOrigin-RevId: 650631975
diff --git a/objectivec/GPBUnknownField.m b/objectivec/GPBUnknownField.m
index f98de4c..8ef9b10 100644
--- a/objectivec/GPBUnknownField.m
+++ b/objectivec/GPBUnknownField.m
@@ -6,13 +6,13 @@
 // https://developers.google.com/open-source/licenses/bsd
 
 #import "GPBUnknownField.h"
-#import "GPBUnknownField_PackagePrivate.h"
-#import "GPBWireFormat.h"
 
 #import "GPBArray.h"
 #import "GPBCodedOutputStream_PackagePrivate.h"
 #import "GPBUnknownFieldSet.h"
+#import "GPBUnknownField_PackagePrivate.h"
 #import "GPBUnknownFields_PackagePrivate.h"
+#import "GPBWireFormat.h"
 
 #define ASSERT_FIELD_TYPE(type)                               \
   if (type_ != type) {                                        \
@@ -20,24 +20,7 @@
                 format:@"GPBUnknownField is the wrong type"]; \
   }
 
-@implementation GPBUnknownField {
- @protected
-  int32_t number_;
-  GPBUnknownFieldType type_;
-
-  union {
-    uint64_t intValue;        // type == Varint, Fixed32, Fixed64
-    NSData *lengthDelimited;  // type == LengthDelimited
-    GPBUnknownFields *group;  // type == Group
-    struct {                  // type == Legacy
-      GPBUInt64Array *mutableVarintList;
-      GPBUInt32Array *mutableFixed32List;
-      GPBUInt64Array *mutableFixed64List;
-      NSMutableArray<NSData *> *mutableLengthDelimitedList;
-      NSMutableArray<GPBUnknownFieldSet *> *mutableGroupList;
-    } legacy;
-  } storage_;
-}
+@implementation GPBUnknownField
 
 @synthesize number = number_;
 @synthesize type = type_;
@@ -289,90 +272,57 @@
 }
 
 - (void)writeToOutput:(GPBCodedOutputStream *)output {
-  switch (type_) {
-    case GPBUnknownFieldTypeVarint:
-      [output writeUInt64:number_ value:storage_.intValue];
-      break;
-    case GPBUnknownFieldTypeFixed32:
-      [output writeFixed32:number_ value:(uint32_t)storage_.intValue];
-      break;
-    case GPBUnknownFieldTypeFixed64:
-      [output writeFixed64:number_ value:storage_.intValue];
-      break;
-    case GPBUnknownFieldTypeLengthDelimited:
-      [output writeBytes:number_ value:storage_.lengthDelimited];
-      break;
-    case GPBUnknownFieldTypeGroup:
-      [output writeRawVarint32:GPBWireFormatMakeTag(number_, GPBWireFormatStartGroup)];
-      [storage_.group writeToCodedOutputStream:output];
-      [output writeRawVarint32:GPBWireFormatMakeTag(number_, GPBWireFormatEndGroup)];
-      break;
-    case GPBUnknownFieldTypeLegacy: {
-      NSUInteger count = storage_.legacy.mutableVarintList.count;
-      if (count > 0) {
-        [output writeUInt64Array:number_ values:storage_.legacy.mutableVarintList tag:0];
-      }
-      count = storage_.legacy.mutableFixed32List.count;
-      if (count > 0) {
-        [output writeFixed32Array:number_ values:storage_.legacy.mutableFixed32List tag:0];
-      }
-      count = storage_.legacy.mutableFixed64List.count;
-      if (count > 0) {
-        [output writeFixed64Array:number_ values:storage_.legacy.mutableFixed64List tag:0];
-      }
-      count = storage_.legacy.mutableLengthDelimitedList.count;
-      if (count > 0) {
-        [output writeBytesArray:number_ values:storage_.legacy.mutableLengthDelimitedList];
-      }
-      count = storage_.legacy.mutableGroupList.count;
-      if (count > 0) {
-        [output writeUnknownGroupArray:number_ values:storage_.legacy.mutableGroupList];
-      }
-    }
+  ASSERT_FIELD_TYPE(GPBUnknownFieldTypeLegacy);
+  NSUInteger count = storage_.legacy.mutableVarintList.count;
+  if (count > 0) {
+    [output writeUInt64Array:number_ values:storage_.legacy.mutableVarintList tag:0];
+  }
+  count = storage_.legacy.mutableFixed32List.count;
+  if (count > 0) {
+    [output writeFixed32Array:number_ values:storage_.legacy.mutableFixed32List tag:0];
+  }
+  count = storage_.legacy.mutableFixed64List.count;
+  if (count > 0) {
+    [output writeFixed64Array:number_ values:storage_.legacy.mutableFixed64List tag:0];
+  }
+  count = storage_.legacy.mutableLengthDelimitedList.count;
+  if (count > 0) {
+    [output writeBytesArray:number_ values:storage_.legacy.mutableLengthDelimitedList];
+  }
+  count = storage_.legacy.mutableGroupList.count;
+  if (count > 0) {
+    [output writeUnknownGroupArray:number_ values:storage_.legacy.mutableGroupList];
   }
 }
 
 - (size_t)serializedSize {
-  switch (type_) {
-    case GPBUnknownFieldTypeVarint:
-      return GPBComputeUInt64Size(number_, storage_.intValue);
-    case GPBUnknownFieldTypeFixed32:
-      return GPBComputeFixed32Size(number_, (uint32_t)storage_.intValue);
-    case GPBUnknownFieldTypeFixed64:
-      return GPBComputeFixed64Size(number_, storage_.intValue);
-    case GPBUnknownFieldTypeLengthDelimited:
-      return GPBComputeBytesSize(number_, storage_.lengthDelimited);
-    case GPBUnknownFieldTypeGroup:
-      return (GPBComputeTagSize(number_) * 2) + [storage_.group serializedSize];
-    case GPBUnknownFieldTypeLegacy: {
-      __block size_t result = 0;
-      int32_t number = number_;
-      [storage_.legacy.mutableVarintList
-          enumerateValuesWithBlock:^(uint64_t value, __unused NSUInteger idx, __unused BOOL *stop) {
-            result += GPBComputeUInt64Size(number, value);
-          }];
+  ASSERT_FIELD_TYPE(GPBUnknownFieldTypeLegacy);
+  __block size_t result = 0;
+  int32_t number = number_;
+  [storage_.legacy.mutableVarintList
+      enumerateValuesWithBlock:^(uint64_t value, __unused NSUInteger idx, __unused BOOL *stop) {
+        result += GPBComputeUInt64Size(number, value);
+      }];
 
-      [storage_.legacy.mutableFixed32List
-          enumerateValuesWithBlock:^(uint32_t value, __unused NSUInteger idx, __unused BOOL *stop) {
-            result += GPBComputeFixed32Size(number, value);
-          }];
+  [storage_.legacy.mutableFixed32List
+      enumerateValuesWithBlock:^(uint32_t value, __unused NSUInteger idx, __unused BOOL *stop) {
+        result += GPBComputeFixed32Size(number, value);
+      }];
 
-      [storage_.legacy.mutableFixed64List
-          enumerateValuesWithBlock:^(uint64_t value, __unused NSUInteger idx, __unused BOOL *stop) {
-            result += GPBComputeFixed64Size(number, value);
-          }];
+  [storage_.legacy.mutableFixed64List
+      enumerateValuesWithBlock:^(uint64_t value, __unused NSUInteger idx, __unused BOOL *stop) {
+        result += GPBComputeFixed64Size(number, value);
+      }];
 
-      for (NSData *data in storage_.legacy.mutableLengthDelimitedList) {
-        result += GPBComputeBytesSize(number, data);
-      }
-
-      for (GPBUnknownFieldSet *set in storage_.legacy.mutableGroupList) {
-        result += GPBComputeUnknownGroupSize(number, set);
-      }
-
-      return result;
-    }
+  for (NSData *data in storage_.legacy.mutableLengthDelimitedList) {
+    result += GPBComputeBytesSize(number, data);
   }
+
+  for (GPBUnknownFieldSet *set in storage_.legacy.mutableGroupList) {
+    result += GPBComputeUnknownGroupSize(number, set);
+  }
+
+  return result;
 }
 
 - (void)writeAsMessageSetExtensionToOutput:(GPBCodedOutputStream *)output {
diff --git a/objectivec/GPBUnknownField_PackagePrivate.h b/objectivec/GPBUnknownField_PackagePrivate.h
index e95358f..117dcb9 100644
--- a/objectivec/GPBUnknownField_PackagePrivate.h
+++ b/objectivec/GPBUnknownField_PackagePrivate.h
@@ -11,7 +11,24 @@
 
 @class GPBCodedOutputStream;
 
-@interface GPBUnknownField ()
+@interface GPBUnknownField () {
+ @package
+  int32_t number_;
+  GPBUnknownFieldType type_;
+
+  union {
+    uint64_t intValue;                 // type == Varint, Fixed32, Fixed64
+    NSData *_Nonnull lengthDelimited;  // type == LengthDelimited
+    GPBUnknownFields *_Nonnull group;  // type == Group
+    struct {                           // type == Legacy
+      GPBUInt64Array *_Null_unspecified mutableVarintList;
+      GPBUInt32Array *_Null_unspecified mutableFixed32List;
+      GPBUInt64Array *_Null_unspecified mutableFixed64List;
+      NSMutableArray<NSData *> *_Null_unspecified mutableLengthDelimitedList;
+      NSMutableArray<GPBUnknownFieldSet *> *_Null_unspecified mutableGroupList;
+    } legacy;
+  } storage_;
+}
 
 - (nonnull instancetype)initWithNumber:(int32_t)number varint:(uint64_t)varint;
 - (nonnull instancetype)initWithNumber:(int32_t)number fixed32:(uint32_t)fixed32;
diff --git a/objectivec/GPBUnknownFields.m b/objectivec/GPBUnknownFields.m
index ee33de1..e24e07b 100644
--- a/objectivec/GPBUnknownFields.m
+++ b/objectivec/GPBUnknownFields.m
@@ -24,14 +24,15 @@
     [NSException raise:NSInvalidArgumentException format:@"Not a valid field number."]; \
   }
 
-@interface GPBUnknownFields ()
-- (BOOL)mergeFromInputStream:(nonnull GPBCodedInputStream *)input endTag:(uint32_t)endTag;
-@end
+// TODO: Consider using on other functions to reduce bloat when
+// some compiler optimizations are enabled.
+#define GPB_NOINLINE __attribute__((noinline))
 
-@implementation GPBUnknownFields {
+@interface GPBUnknownFields () {
  @package
   NSMutableArray<GPBUnknownField *> *fields_;
 }
+@end
 
 // Direct access is use for speed, to avoid even internally declaring things
 // read/write, etc. The warning is enabled in the project to ensure code calling
@@ -39,6 +40,157 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdirect-ivar-access"
 
+GPB_NOINLINE
+static size_t ComputeSerializeSize(GPBUnknownFields *_Nonnull self) {
+  size_t result = 0;
+  for (GPBUnknownField *field in self->fields_) {
+    uint32_t fieldNumber = field->number_;
+    switch (field->type_) {
+      case GPBUnknownFieldTypeVarint:
+        result += GPBComputeUInt64Size(fieldNumber, field->storage_.intValue);
+        break;
+      case GPBUnknownFieldTypeFixed32:
+        result += GPBComputeFixed32Size(fieldNumber, (uint32_t)field->storage_.intValue);
+        break;
+      case GPBUnknownFieldTypeFixed64:
+        result += GPBComputeFixed64Size(fieldNumber, field->storage_.intValue);
+        break;
+      case GPBUnknownFieldTypeLengthDelimited:
+        result += GPBComputeBytesSize(fieldNumber, field->storage_.lengthDelimited);
+        break;
+      case GPBUnknownFieldTypeGroup:
+        result +=
+            (GPBComputeTagSize(fieldNumber) * 2) + ComputeSerializeSize(field->storage_.group);
+        break;
+      case GPBUnknownFieldTypeLegacy:
+#if defined(DEBUG) && DEBUG
+        NSCAssert(NO, @"Internal error within the library");
+#endif
+        break;
+    }
+  }
+  return result;
+}
+
+GPB_NOINLINE
+static void WriteToCoddedOutputStream(GPBUnknownFields *_Nonnull self,
+                                      GPBCodedOutputStream *_Nonnull output) {
+  for (GPBUnknownField *field in self->fields_) {
+    uint32_t fieldNumber = field->number_;
+    switch (field->type_) {
+      case GPBUnknownFieldTypeVarint:
+        [output writeUInt64:fieldNumber value:field->storage_.intValue];
+        break;
+      case GPBUnknownFieldTypeFixed32:
+        [output writeFixed32:fieldNumber value:(uint32_t)field->storage_.intValue];
+        break;
+      case GPBUnknownFieldTypeFixed64:
+        [output writeFixed64:fieldNumber value:field->storage_.intValue];
+        break;
+      case GPBUnknownFieldTypeLengthDelimited:
+        [output writeBytes:fieldNumber value:field->storage_.lengthDelimited];
+        break;
+      case GPBUnknownFieldTypeGroup:
+        [output writeRawVarint32:GPBWireFormatMakeTag(fieldNumber, GPBWireFormatStartGroup)];
+        WriteToCoddedOutputStream(field->storage_.group, output);
+        [output writeRawVarint32:GPBWireFormatMakeTag(fieldNumber, GPBWireFormatEndGroup)];
+        break;
+      case GPBUnknownFieldTypeLegacy:
+#if defined(DEBUG) && DEBUG
+        NSCAssert(NO, @"Internal error within the library");
+#endif
+        break;
+    }
+  }
+}
+
+GPB_NOINLINE
+static BOOL MergeFromInputStream(GPBUnknownFields *self, GPBCodedInputStream *input,
+                                 uint32_t endTag) {
+#if defined(DEBUG) && DEBUG
+  NSCAssert(endTag == 0 || GPBWireFormatGetTagWireType(endTag) == GPBWireFormatEndGroup,
+            @"Internal error:Invalid end tag: %u", endTag);
+#endif
+  GPBCodedInputStreamState *state = &input->state_;
+  NSMutableArray<GPBUnknownField *> *fields = self->fields_;
+  @try {
+    while (YES) {
+      uint32_t tag = GPBCodedInputStreamReadTag(state);
+      if (tag == endTag) {
+        return YES;
+      }
+      if (tag == 0) {
+        // Reached end of input without finding the end tag.
+        return NO;
+      }
+      GPBWireFormat wireType = GPBWireFormatGetTagWireType(tag);
+      int32_t fieldNumber = GPBWireFormatGetTagFieldNumber(tag);
+      switch (wireType) {
+        case GPBWireFormatVarint: {
+          uint64_t value = GPBCodedInputStreamReadInt64(state);
+          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
+                                                                    varint:value];
+          [fields addObject:field];
+          [field release];
+          break;
+        }
+        case GPBWireFormatFixed32: {
+          uint32_t value = GPBCodedInputStreamReadFixed32(state);
+          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
+                                                                   fixed32:value];
+          [fields addObject:field];
+          [field release];
+          break;
+        }
+        case GPBWireFormatFixed64: {
+          uint64_t value = GPBCodedInputStreamReadFixed64(state);
+          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
+                                                                   fixed64:value];
+          [fields addObject:field];
+          [field release];
+          break;
+        }
+        case GPBWireFormatLengthDelimited: {
+          NSData *data = GPBCodedInputStreamReadRetainedBytes(state);
+          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
+                                                           lengthDelimited:data];
+          [fields addObject:field];
+          [field release];
+          [data release];
+          break;
+        }
+        case GPBWireFormatStartGroup: {
+          GPBUnknownFields *group = [[GPBUnknownFields alloc] init];
+          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber group:group];
+          [fields addObject:field];
+          [field release];
+          [group release];  // Still will be held in the field/fields.
+          uint32_t endGroupTag = GPBWireFormatMakeTag(fieldNumber, GPBWireFormatEndGroup);
+          if (MergeFromInputStream(group, input, endGroupTag)) {
+            GPBCodedInputStreamCheckLastTagWas(state, endGroupTag);
+          } else {
+            [NSException
+                 raise:NSInternalInconsistencyException
+                format:@"Internal error: Unknown field data for nested group was malformed."];
+          }
+          break;
+        }
+        case GPBWireFormatEndGroup:
+          [NSException raise:NSInternalInconsistencyException
+                      format:@"Unexpected end group tag: %u", tag];
+          break;
+      }
+    }
+  } @catch (NSException *exception) {
+#if defined(DEBUG) && DEBUG
+    NSLog(@"%@: Internal exception while parsing unknown data, this shouldn't happen!: %@",
+          [self class], exception);
+#endif
+  }
+}
+
+@implementation GPBUnknownFields
+
 - (instancetype)initFromMessage:(nonnull GPBMessage *)message {
   self = [super init];
   if (self) {
@@ -50,7 +202,7 @@
       GPBCodedInputStream *input =
           [[GPBCodedInputStream alloc] initWithData:[legacyUnknownFields data]];
       // Parse until the end of the data (tag will be zero).
-      if (![self mergeFromInputStream:input endTag:0]) {
+      if (!MergeFromInputStream(self, input, 0)) {
         [input release];
         [self release];
         [NSException raise:NSInternalInconsistencyException
@@ -177,29 +329,15 @@
 
 #pragma mark - Internal Methods
 
-- (size_t)serializedSize {
-  size_t result = 0;
-  for (GPBUnknownField *field in self->fields_) {
-    result += [field serializedSize];
-  }
-  return result;
-}
-
-- (void)writeToCodedOutputStream:(GPBCodedOutputStream *)output {
-  for (GPBUnknownField *field in fields_) {
-    [field writeToOutput:output];
-  }
-}
-
 - (NSData *)serializeAsData {
   if (fields_.count == 0) {
     return [NSData data];
   }
-  size_t expectedSize = [self serializedSize];
+  size_t expectedSize = ComputeSerializeSize(self);
   NSMutableData *data = [NSMutableData dataWithLength:expectedSize];
   GPBCodedOutputStream *stream = [[GPBCodedOutputStream alloc] initWithData:data];
   @try {
-    [self writeToCodedOutputStream:stream];
+    WriteToCoddedOutputStream(self, stream);
     [stream flush];
   } @catch (NSException *exception) {
 #if defined(DEBUG) && DEBUG
@@ -213,88 +351,6 @@
   return data;
 }
 
-- (BOOL)mergeFromInputStream:(nonnull GPBCodedInputStream *)input endTag:(uint32_t)endTag {
-#if defined(DEBUG) && DEBUG
-  NSAssert(endTag == 0 || GPBWireFormatGetTagWireType(endTag) == GPBWireFormatEndGroup,
-           @"Internal error:Invalid end tag: %u", endTag);
-#endif
-  GPBCodedInputStreamState *state = &input->state_;
-  @try {
-    while (YES) {
-      uint32_t tag = GPBCodedInputStreamReadTag(state);
-      if (tag == endTag) {
-        return YES;
-      }
-      if (tag == 0) {
-        // Reached end of input without finding the end tag.
-        return NO;
-      }
-      GPBWireFormat wireType = GPBWireFormatGetTagWireType(tag);
-      int32_t fieldNumber = GPBWireFormatGetTagFieldNumber(tag);
-      switch (wireType) {
-        case GPBWireFormatVarint: {
-          uint64_t value = GPBCodedInputStreamReadInt64(state);
-          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
-                                                                    varint:value];
-          [fields_ addObject:field];
-          [field release];
-          break;
-        }
-        case GPBWireFormatFixed32: {
-          uint32_t value = GPBCodedInputStreamReadFixed32(state);
-          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
-                                                                   fixed32:value];
-          [fields_ addObject:field];
-          [field release];
-          break;
-        }
-        case GPBWireFormatFixed64: {
-          uint64_t value = GPBCodedInputStreamReadFixed64(state);
-          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
-                                                                   fixed64:value];
-          [fields_ addObject:field];
-          [field release];
-          break;
-        }
-        case GPBWireFormatLengthDelimited: {
-          NSData *data = GPBCodedInputStreamReadRetainedBytes(state);
-          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber
-                                                           lengthDelimited:data];
-          [fields_ addObject:field];
-          [field release];
-          [data release];
-          break;
-        }
-        case GPBWireFormatStartGroup: {
-          GPBUnknownFields *group = [[GPBUnknownFields alloc] init];
-          GPBUnknownField *field = [[GPBUnknownField alloc] initWithNumber:fieldNumber group:group];
-          [fields_ addObject:field];
-          [field release];
-          [group release];  // Still will be held in the field/fields_.
-          uint32_t endGroupTag = GPBWireFormatMakeTag(fieldNumber, GPBWireFormatEndGroup);
-          if ([group mergeFromInputStream:input endTag:endGroupTag]) {
-            GPBCodedInputStreamCheckLastTagWas(state, endGroupTag);
-          } else {
-            [NSException
-                 raise:NSInternalInconsistencyException
-                format:@"Internal error: Unknown field data for nested group was malformed."];
-          }
-          break;
-        }
-        case GPBWireFormatEndGroup:
-          [NSException raise:NSInternalInconsistencyException
-                      format:@"Unexpected end group tag: %u", tag];
-          break;
-      }
-    }
-  } @catch (NSException *exception) {
-#if defined(DEBUG) && DEBUG
-    NSLog(@"%@: Internal exception while parsing unknown data, this shouldn't happen!: %@",
-          [self class], exception);
-#endif
-  }
-}
-
 @end
 
 @implementation GPBUnknownFields (AccessHelpers)
@@ -352,6 +408,6 @@
   return nil;
 }
 
-#pragma clang diagnostic pop
-
 @end
+
+#pragma clang diagnostic pop
diff --git a/objectivec/GPBUnknownFields_PackagePrivate.h b/objectivec/GPBUnknownFields_PackagePrivate.h
index dc63d3a..ba2f251 100644
--- a/objectivec/GPBUnknownFields_PackagePrivate.h
+++ b/objectivec/GPBUnknownFields_PackagePrivate.h
@@ -13,8 +13,6 @@
 
 @interface GPBUnknownFields ()
 
-- (size_t)serializedSize;
 - (nonnull NSData *)serializeAsData;
-- (void)writeToCodedOutputStream:(nonnull GPBCodedOutputStream *)output;
 
 @end