Repository: dart-lang/native
Pull request #2060: [ffigen] Protocol polish
Author: login: liamappelbe
Labels: name: package:ffigen
name: skip-changelog-check
name: package:objective_c
Created: 2025-03-04T01:02:18Z | Merged: 2025-03-06T00:35:24Z

Clean up the class object when the implementation object and the builder have been destroyed, by wrapping it in a `DOBJCDartProtocolClass` object that calls `objc_disposeClassPair` in its `dispose` method, and having the builder and instances hold a reference to this object.

Also, since we're not allowing adding more methods after building the protocol, we don't have to copy the method table during instantiation anymore.

Finally, re-add a bunch of other GC tests that I accidentally deleted.

Fixes #2042

Linked issue #2042: [ffigen] Protocol polish
https://github.com/dart-lang/native/pull/2029#discussion_r1974358031

Fix the class leak and stop copying the method dictionary.

Diff:
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
index c44201398b..e14636f323 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
@@ -43,6 +43,7 @@ class ObjCBuiltInFunctions {
   static const protocolMethod = ObjCImport('ObjCProtocolMethod');
   static const protocolListenableMethod =
       ObjCImport('ObjCProtocolListenableMethod');
+  static const protocolClass = ObjCImport('Protocol');
   static const protocolBuilder = ObjCImport('ObjCProtocolBuilder');
   static const unimplementedOptionalMethodException =
       ObjCImport('UnimplementedOptionalMethodException');
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
index 0ac84ac8cf..15ea7aa89c 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
@@ -62,6 +62,7 @@ class ObjCProtocol extends BindingType with ObjCMethods {
 
   @override
   BindingString toBindingString(Writer w) {
+    final protocolClass = ObjCBuiltInFunctions.protocolClass.gen(w);
     final protocolBase = ObjCBuiltInFunctions.protocolBase.gen(w);
     final protocolMethod = ObjCBuiltInFunctions.protocolMethod.gen(w);
     final protocolListenableMethod =
@@ -180,6 +181,10 @@ interface class $name extends $protocolBase $impls{
       buildArgs.add('bool \$keepIsolateAlive = true');
       final args = '{${buildArgs.join(', ')}}';
       final builders = '''
+  /// Returns the [$protocolClass] object for this protocol.
+  static $protocolClass get \$protocol =>
+      $protocolClass.castFromPointer(${_protocolPointer.name}.cast());
+
   /// Builds an object that implements the $originalName protocol. To implement
   /// multiple protocols, use [addToBuilder] or [$protocolBuilder] directly.
   ///
@@ -188,6 +193,7 @@ interface class $name extends $protocolBase $impls{
   static $name implement($args) {
     final builder = $protocolBuilder(debugName: '$originalName');
     $buildImplementations
+    builder.addProtocol(\$protocol);
     return $name.castFrom(builder.build(keepIsolateAlive: \$keepIsolateAlive));
   }
 
@@ -197,6 +203,7 @@ interface class $name extends $protocolBase $impls{
   /// Note: You cannot call this method after you have called `builder.build`.
   static void addToBuilder($protocolBuilder builder, $args) {
     $buildImplementations
+    builder.addProtocol(\$protocol);
   }
 ''';
 
@@ -212,6 +219,7 @@ interface class $name extends $protocolBase $impls{
   static $name implementAsListener($args) {
     final builder = $protocolBuilder(debugName: '$originalName');
     $buildListenerImplementations
+    builder.addProtocol(\$protocol);
     return $name.castFrom(builder.build(keepIsolateAlive: \$keepIsolateAlive));
   }
 
@@ -222,6 +230,7 @@ interface class $name extends $protocolBase $impls{
   /// Note: You cannot call this method after you have called `builder.build`.
   static void addToBuilderAsListener($protocolBuilder builder, $args) {
     $buildListenerImplementations
+    builder.addProtocol(\$protocol);
   }
 
   /// Builds an object that implements the $originalName protocol. To implement
@@ -233,6 +242,7 @@ interface class $name extends $protocolBase $impls{
   static $name implementAsBlocking($args) {
     final builder = $protocolBuilder(debugName: '$originalName');
     $buildBlockingImplementations
+    builder.addProtocol(\$protocol);
     return $name.castFrom(builder.build(keepIsolateAlive: \$keepIsolateAlive));
   }
 
@@ -243,6 +253,7 @@ interface class $name extends $protocolBase $impls{
   /// Note: You cannot call this method after you have called `builder.build`.
   static void addToBuilderAsBlocking($protocolBuilder builder, $args) {
     $buildBlockingImplementations
+    builder.addProtocol(\$protocol);
   }
 ''';
       }
diff --git a/pkgs/ffigen/test/native_objc_test/protocol_config.yaml b/pkgs/ffigen/test/native_objc_test/protocol_config.yaml
index edb1319c00..f070b1c2af 100644
--- a/pkgs/ffigen/test/native_objc_test/protocol_config.yaml
+++ b/pkgs/ffigen/test/native_objc_test/protocol_config.yaml
@@ -5,6 +5,12 @@ output:
   bindings: 'protocol_bindings.dart'
   objc-bindings: 'protocol_bindings.m'
 exclude-all-by-default: true
+functions:
+  include:
+    - getClass
+    - getClassName
+    - objc_autoreleasePoolPop
+    - objc_autoreleasePoolPush
 objc-interfaces:
   include:
     - ProtocolConsumer
diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.dart b/pkgs/ffigen/test/native_objc_test/protocol_test.dart
index 33fb85e25e..232c443f58 100644
--- a/pkgs/ffigen/test/native_objc_test/protocol_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/protocol_test.dart
@@ -12,6 +12,7 @@ import 'dart:isolate';
 
 import 'package:ffi/ffi.dart';
 import 'package:objective_c/objective_c.dart';
+import 'package:objective_c/src/internal.dart' show getProtocol;
 import 'package:test/test.dart';
 
 import '../test_utils.dart';
@@ -24,13 +25,15 @@ typedef VoidMethodBlock = ObjCBlock_ffiVoid_ffiVoid_Int32;
 typedef OtherMethodBlock = ObjCBlock_Int32_ffiVoid_Int32_Int32_Int32_Int32;
 
 void main() {
+  late ProtocolTestObjCLibrary lib;
+
   group('protocol', () {
     setUpAll(() {
       // TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
       DynamicLibrary.open('../objective_c/test/objective_c.dylib');
       final dylib = File('test/native_objc_test/objc_test.dylib');
       verifySetupFile(dylib);
-      DynamicLibrary.open(dylib.absolute.path);
+      lib = ProtocolTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
       generateBindingsForCoverage('protocol');
     });
 
@@ -126,6 +129,9 @@ void main() {
           },
         );
 
+        expect(MyProtocol.conformsTo(myProtocol), isTrue);
+        expect(SecondaryProtocol.conformsTo(myProtocol), isFalse);
+
         // Required instance method.
         final result = consumer.callInstanceMethod_(myProtocol);
         expect(result.toDartString(), 'MyProtocol: Hello from ObjC: 3.14');
@@ -157,6 +163,9 @@ void main() {
         final SecondaryProtocol asSecondaryProtocol =
             SecondaryProtocol.castFrom(protocolImpl);
 
+        expect(MyProtocol.conformsTo(protocolImpl), isTrue);
+        expect(SecondaryProtocol.conformsTo(protocolImpl), isTrue);
+
         // Required instance method.
         final result = consumer.callInstanceMethod_(asMyProtocol);
         expect(result.toDartString(), 'ProtocolBuilder: Hello from ObjC: 3.14');
@@ -413,6 +422,71 @@ void main() {
       expect(UnusedProtocol.conformsTo(inst), isFalse);
     });
 
+    test('Threading stress test', () async {
+      final consumer = ProtocolConsumer();
+      final completer = Completer<void>();
+      int count = 0;
+
+      final protocolBuilder = ObjCProtocolBuilder();
+      MyProtocol.voidMethod_.implementAsListener(protocolBuilder, (int x) {
+        expect(x, 123);
+        ++count;
+        if (count == 1000) completer.complete();
+      });
+
+      final protocol = protocolBuilder.build();
+      final MyProtocol asMyProtocol = MyProtocol.castFrom(protocol);
+
+      for (int i = 0; i < 1000; ++i) {
+        consumer.callMethodOnRandomThread_(asMyProtocol);
+      }
+      await completer.future;
+      expect(count, 1000);
+    });
+
+    (NSObject, Pointer<ObjCBlockImpl>) blockRefCountTestInner() {
+      final pool = lib.objc_autoreleasePoolPush();
+      final protocolBuilder = ObjCProtocolBuilder();
+
+      final block = InstanceMethodBlock.fromFunction(
+          (Pointer<Void> p, NSString s, double x) => 'Hello'.toNSString());
+      MyProtocol.instanceMethod_withDouble_
+          .implementWithBlock(protocolBuilder, block);
+      final protocol = protocolBuilder.build();
+      lib.objc_autoreleasePoolPop(pool);
+
+      final blockPtr = block.ref.pointer;
+
+      // There are 2 references to the block. One owned by the Dart wrapper
+      // object, and the other owned by the protocol.
+      doGC();
+      expect(blockRetainCount(blockPtr), 2);
+
+      return (protocol, blockPtr);
+    }
+
+    Pointer<ObjCBlockImpl> blockRefCountTest() {
+      final (protocol, blockPtr) = blockRefCountTestInner();
+
+      // The Dart side block pointer has gone out of scope, but the protocol
+      // still owns a reference to it.
+      doGC();
+      expect(blockRetainCount(blockPtr), 1);
+
+      expect(protocol, isNotNull); // Force protocol to stay in scope.
+
+      return blockPtr;
+    }
+
+    test('Block ref counting', () {
+      final blockPtr = blockRefCountTest();
+
+      // The protocol object has gone out of scope, so it should be cleaned up.
+      // So should the block.
+      doGC();
+      expect(blockRetainCount(blockPtr), 0);
+    }, skip: !canDoGC);
+
     test('keepIsolateAlive', () async {
       final isolateSendPort = Completer<SendPort>();
       final protosCreated = Completer<void>();
@@ -475,5 +549,70 @@ void main() {
 
       receivePort.close();
     }, skip: !canDoGC);
+
+    test('class disposal, builder first', () {
+      final pool = lib.objc_autoreleasePoolPush();
+      ObjCProtocolBuilder? protocolBuilder =
+          ObjCProtocolBuilder(debugName: 'Foo');
+
+      NSObject? protocol = protocolBuilder.build();
+      final clazz = lib.getClass(protocol);
+      expect(lib.getClassName(clazz).cast<Utf8>().toDartString(),
+          startsWith('Foo'));
+      expect(isValidClass(clazz), isTrue);
+      lib.objc_autoreleasePoolPop(pool);
+
+      protocolBuilder = null;
+      doGC();
+      expect(isValidClass(clazz), isTrue);
+
+      protocol = null;
+      doGC();
+      expect(isValidClass(clazz), isFalse);
+    }, skip: !canDoGC);
+
+    test('class disposal, instance first', () {
+      final pool = lib.objc_autoreleasePoolPush();
+      ObjCProtocolBuilder? protocolBuilder =
+          ObjCProtocolBuilder(debugName: 'Foo');
+
+      NSObject? protocol = protocolBuilder.build();
+      final clazz = lib.getClass(protocol);
+      expect(lib.getClassName(clazz).cast<Utf8>().toDartString(),
+          startsWith('Foo'));
+      expect(isValidClass(clazz), isTrue);
+      lib.objc_autoreleasePoolPop(pool);
+
+      protocolBuilder = null;
+      doGC();
+      expect(isValidClass(clazz), isTrue);
+
+      protocol = null;
+      doGC();
+      expect(isValidClass(clazz), isFalse);
+    }, skip: !canDoGC);
+
+    test('adding more methods after build', () {
+      final protocolBuilder = ObjCProtocolBuilder();
+
+      MyProtocol.addToBuilder(
+        protocolBuilder,
+        instanceMethod_withDouble_: (NSString s, double x) {
+          return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString();
+        },
+        optionalMethod_: (SomeStruct s) {
+          return s.y - s.x;
+        },
+      );
+
+      final protocolImpl = protocolBuilder.build();
+
+      expect(
+          () => SecondaryProtocol.addToBuilder(protocolBuilder,
+                  otherMethod_b_c_d_: (int a, int b, int c, int d) {
+                return a * b * c * d;
+              }),
+          throwsA(isA<StateError>()));
+    });
   });
 }
diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.h b/pkgs/ffigen/test/native_objc_test/protocol_test.h
index 769d97d6aa..4a0412285d 100644
--- a/pkgs/ffigen/test/native_objc_test/protocol_test.h
+++ b/pkgs/ffigen/test/native_objc_test/protocol_test.h
@@ -5,6 +5,11 @@
 #import <Foundation/NSObject.h>
 #import <Foundation/NSString.h>
 
+const char* getClassName(void* cls);
+void* getClass(id object);
+void objc_autoreleasePoolPop(void *pool);
+void *objc_autoreleasePoolPush();
+
 typedef struct {
   int32_t x;
   int32_t y;
diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.m b/pkgs/ffigen/test/native_objc_test/protocol_test.m
index dcd55ad59e..1f1afc221e 100644
--- a/pkgs/ffigen/test/native_objc_test/protocol_test.m
+++ b/pkgs/ffigen/test/native_objc_test/protocol_test.m
@@ -8,6 +8,16 @@
 
 #include "protocol_test.h"
 
+const char* class_getName(Class cls);
+
+const char* getClassName(void* cls) {
+  return class_getName((__bridge Class)cls);
+}
+
+void* getClass(id object) {
+  return (__bridge void*)[object class];
+}
+
 @implementation ProtocolConsumer : NSObject
 - (NSString*)callInstanceMethod:(id<SuperProtocol>)protocol {
   return [protocol instanceMethod:@"Hello from ObjC" withDouble:3.14];
diff --git a/pkgs/ffigen/test/native_objc_test/util.dart b/pkgs/ffigen/test/native_objc_test/util.dart
index 11d4602e0d..4fef206b35 100644
--- a/pkgs/ffigen/test/native_objc_test/util.dart
+++ b/pkgs/ffigen/test/native_objc_test/util.dart
@@ -93,3 +93,6 @@ int objectRetainCount(Pointer<ObjCObject> object) {
   if (!internal_for_testing.isValidClass(clazz)) return 0;
   return _getObjectRetainCount(object.cast());
 }
+
+bool isValidClass(Pointer<Void> clazz) =>
+    internal_for_testing.isValidClass(clazz.cast(), forceReloadClasses: true);
diff --git a/pkgs/objective_c/ffigen_c.yaml b/pkgs/objective_c/ffigen_c.yaml
index 5f6b7a9427..022ee7ecc1 100644
--- a/pkgs/objective_c/ffigen_c.yaml
+++ b/pkgs/objective_c/ffigen_c.yaml
@@ -22,7 +22,6 @@ functions:
     - 'protocol_getMethodDescription'
     - 'protocol_getName'
     - 'newFinalizableHandle'
-    - 'class_addMethod'
     - 'DOBJC_.*'
   leaf:
     include:
@@ -56,10 +55,7 @@ functions:
     'objc_msgSend_stret': 'msgSendStret'
     'object_getClass': 'getObjectClass'
     'objc_copyClassList': 'copyClassList'
-    'objc_allocateClassPair': 'allocateClassPair'
-    'objc_registerClassPair': 'registerClassPair'
     'objc_getProtocol': 'getProtocol'
-    'class_addMethod': 'addMethod'
     'protocol_getMethodDescription': 'getMethodDescription'
     'protocol_getName': 'getProtocolName'
 globals:
diff --git a/pkgs/objective_c/lib/src/c_bindings_generated.dart b/pkgs/objective_c/lib/src/c_bindings_generated.dart
index c62d86553d..25e25a9201 100644
--- a/pkgs/objective_c/lib/src/c_bindings_generated.dart
+++ b/pkgs/objective_c/lib/src/c_bindings_generated.dart
@@ -56,30 +56,6 @@ external ffi.Array<ffi.Pointer<ffi.Void>> NSConcreteMallocBlock;
 @ffi.Native<ffi.Array<ffi.Pointer<ffi.Void>>>(symbol: '_NSConcreteStackBlock')
 external ffi.Array<ffi.Pointer<ffi.Void>> NSConcreteStackBlock;
 
-@ffi.Native<
-    ffi.Bool Function(
-        ffi.Pointer<ObjCObject>,
-        ffi.Pointer<ObjCSelector>,
-        ffi.Pointer<ffi.Void>,
-        ffi.Pointer<ffi.Char>)>(symbol: 'class_addMethod', isLeaf: true)
-external bool addMethod(
-  ffi.Pointer<ObjCObject> cls,
-  ffi.Pointer<ObjCSelector> name,
-  ffi.Pointer<ffi.Void> imp,
-  ffi.Pointer<ffi.Char> types,
-);
-
-@ffi.Native<
-    ffi.Pointer<ObjCObject> Function(
-        ffi.Pointer<ObjCObject>,
-        ffi.Pointer<ffi.Char>,
-        ffi.Size)>(symbol: 'objc_allocateClassPair', isLeaf: true)
-external ffi.Pointer<ObjCObject> allocateClassPair(
-  ffi.Pointer<ObjCObject> superclass,
-  ffi.Pointer<ffi.Char> name,
-  int extraBytes,
-);
-
 @ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>)>(
     symbol: 'DOBJC_awaitWaiter')
 external void awaitWaiter(
@@ -211,12 +187,6 @@ external ffi.Pointer<ObjCObject> objectRetain(
   ffi.Pointer<ObjCObject> object,
 );
 
-@ffi.Native<ffi.Void Function(ffi.Pointer<ObjCObject>)>(
-    symbol: 'objc_registerClassPair', isLeaf: true)
-external void registerClassPair(
-  ffi.Pointer<ObjCObject> cls,
-);
-
 @ffi.Native<ffi.Pointer<ObjCSelector> Function(ffi.Pointer<ffi.Char>)>(
     symbol: 'sel_registerName', isLeaf: true)
 external ffi.Pointer<ObjCSelector> registerName(
diff --git a/pkgs/objective_c/lib/src/internal.dart b/pkgs/objective_c/lib/src/internal.dart
index 42372e33be..e259908574 100644
--- a/pkgs/objective_c/lib/src/internal.dart
+++ b/pkgs/objective_c/lib/src/internal.dart
@@ -295,8 +295,8 @@ bool _isValidObject(ObjectPtr ptr) {
 
 final _allClasses = <ObjectPtr>{};
 
-bool _isValidClass(ObjectPtr clazz) {
-  if (_allClasses.contains(clazz)) return true;
+bool _isValidClass(ObjectPtr clazz, {bool forceReloadClasses = false}) {
+  if (!forceReloadClasses && _allClasses.contains(clazz)) return true;
 
   // If the class is missing from the list, it either means we haven't created
   // the set yet, or more classes have been loaded since we created the set, or
@@ -446,5 +446,6 @@ BlockPtr wrapBlockingBlock(
 bool blockHasRegisteredClosure(BlockPtr block) =>
     _blockClosureRegistry.containsKey(block.ref.target.address);
 bool isValidBlock(BlockPtr block) => c.isValidBlock(block);
-bool isValidClass(ObjectPtr clazz) => _isValidClass(clazz);
+bool isValidClass(ObjectPtr clazz, {bool forceReloadClasses = false}) =>
+    _isValidClass(clazz, forceReloadClasses: forceReloadClasses);
 bool isValidObject(ObjectPtr object) => _isValidObject(object);
diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart
index 2e9121882e..3d1db4f2ce 100644
--- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart
+++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart
@@ -456,7 +456,7 @@ class DartInputStreamAdapter extends NSInputStream implements NSStreamDelegate {
   }
 }
 
-/// DOBJCDartProtocol
+/// Base class of all classes DOBJCDartProtocolBuilder creates.
 class DartProtocol extends NSObject {
   DartProtocol._(ffi.Pointer<objc.ObjCObject> pointer,
       {bool retain = false, bool release = false})
@@ -551,7 +551,8 @@ class DartProtocol extends NSObject {
   factory DartProtocol() => new$();
 }
 
-/// DOBJCDartProtocolBuilder
+/// Used by the Dart ObjCProtocolBuilder to construct ObjC classes at runtime to
+/// implement protocols.
 class DartProtocolBuilder extends NSObject {
   DartProtocolBuilder._(ffi.Pointer<objc.ObjCObject> pointer,
       {bool retain = false, bool release = false})
@@ -596,6 +597,12 @@ class DartProtocolBuilder extends NSObject {
         retain: false, release: true);
   }
 
+  /// addProtocol:
+  void addProtocol_(Protocol protocol) {
+    _objc_msgSend_xtuoz7(
+        this.ref.pointer, _sel_addProtocol_, protocol.ref.pointer);
+  }
+
   /// autorelease
   DartProtocolBuilder autorelease() {
     final _ret = _objc_msgSend_151sglz(this.ref.pointer, _sel_autorelease);
@@ -603,11 +610,26 @@ class DartProtocolBuilder extends NSObject {
         retain: true, release: true);
   }
 
-  /// implementMethod:withBlock:
-  void implementMethod_withBlock_(
-      ffi.Pointer<objc.ObjCSelector> sel, ffi.Pointer<ffi.Void> block) {
-    _objc_msgSend_1iqj6b6(
-        this.ref.pointer, _sel_implementMethod_withBlock_, sel, block);
+  /// buildInstance:
+  DartProtocol buildInstance_(int port) {
+    final _ret =
+        _objc_msgSend_1ya1kjn(this.ref.pointer, _sel_buildInstance_, port);
+    return DartProtocol.castFromPointer(_ret, retain: true, release: true);
+  }
+
+  /// implementMethod:withBlock:withTrampoline:withSignature:
+  void implementMethod_withBlock_withTrampoline_withSignature_(
+      ffi.Pointer<objc.ObjCSelector> sel,
+      ffi.Pointer<ffi.Void> block,
+      ffi.Pointer<ffi.Void> trampoline,
+      ffi.Pointer<ffi.Char> signature) {
+    _objc_msgSend_1s2gdyk(
+        this.ref.pointer,
+        _sel_implementMethod_withBlock_withTrampoline_withSignature_,
+        sel,
+        block,
+        trampoline,
+        signature);
   }
 
   /// init
@@ -620,6 +642,19 @@ class DartProtocolBuilder extends NSObject {
         retain: false, release: true);
   }
 
+  /// initWithClassName:
+  DartProtocolBuilder initWithClassName_(ffi.Pointer<ffi.Char> name) {
+    final _ret = _objc_msgSend_56zxyn(
+        this.ref.retainAndReturnPointer(), _sel_initWithClassName_, name);
+    return DartProtocolBuilder.castFromPointer(_ret,
+        retain: false, release: true);
+  }
+
+  /// registerClass
+  void registerClass() {
+    _objc_msgSend_1pl9qdv(this.ref.pointer, _sel_registerClass);
+  }
+
   /// retain
   DartProtocolBuilder retain() {
     final _ret = _objc_msgSend_151sglz(this.ref.pointer, _sel_retain);
@@ -1124,6 +1159,10 @@ interface class NSCoding extends objc.ObjCProtocolBase {
         obj.ref.pointer, _sel_conformsToProtocol_, _protocol_NSCoding);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSCoding.cast());
+
   /// Builds an object that implements the NSCoding protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -1136,6 +1175,7 @@ interface class NSCoding extends objc.ObjCProtocolBase {
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSCoding');
     NSCoding.encodeWithCoder_.implement(builder, encodeWithCoder_);
     NSCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
     return NSCoding.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -1150,6 +1190,7 @@ interface class NSCoding extends objc.ObjCProtocolBase {
       bool $keepIsolateAlive = true}) {
     NSCoding.encodeWithCoder_.implement(builder, encodeWithCoder_);
     NSCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSCoding protocol. To implement
@@ -1165,6 +1206,7 @@ interface class NSCoding extends objc.ObjCProtocolBase {
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSCoding');
     NSCoding.encodeWithCoder_.implementAsListener(builder, encodeWithCoder_);
     NSCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
     return NSCoding.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -1180,6 +1222,7 @@ interface class NSCoding extends objc.ObjCProtocolBase {
       bool $keepIsolateAlive = true}) {
     NSCoding.encodeWithCoder_.implementAsListener(builder, encodeWithCoder_);
     NSCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSCoding protocol. To implement
@@ -1195,6 +1238,7 @@ interface class NSCoding extends objc.ObjCProtocolBase {
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSCoding');
     NSCoding.encodeWithCoder_.implementAsBlocking(builder, encodeWithCoder_);
     NSCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
     return NSCoding.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -1210,6 +1254,7 @@ interface class NSCoding extends objc.ObjCProtocolBase {
       bool $keepIsolateAlive = true}) {
     NSCoding.encodeWithCoder_.implementAsBlocking(builder, encodeWithCoder_);
     NSCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
   }
 
   /// encodeWithCoder:
@@ -1299,6 +1344,10 @@ interface class NSCopying extends objc.ObjCProtocolBase {
         obj.ref.pointer, _sel_conformsToProtocol_, _protocol_NSCopying);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSCopying.cast());
+
   /// Builds an object that implements the NSCopying protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -1309,6 +1358,7 @@ interface class NSCopying extends objc.ObjCProtocolBase {
       bool $keepIsolateAlive = true}) {
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSCopying');
     NSCopying.copyWithZone_.implement(builder, copyWithZone_);
+    builder.addProtocol($protocol);
     return NSCopying.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -1321,6 +1371,7 @@ interface class NSCopying extends objc.ObjCProtocolBase {
       {required objc.ObjCObjectBase Function(ffi.Pointer<NSZone>) copyWithZone_,
       bool $keepIsolateAlive = true}) {
     NSCopying.copyWithZone_.implement(builder, copyWithZone_);
+    builder.addProtocol($protocol);
   }
 
   /// copyWithZone:
@@ -3656,6 +3707,10 @@ interface class NSFastEnumeration extends objc.ObjCProtocolBase {
         obj.ref.pointer, _sel_conformsToProtocol_, _protocol_NSFastEnumeration);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSFastEnumeration.cast());
+
   /// Builds an object that implements the NSFastEnumeration protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -3669,6 +3724,7 @@ interface class NSFastEnumeration extends objc.ObjCProtocolBase {
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSFastEnumeration');
     NSFastEnumeration.countByEnumeratingWithState_objects_count_
         .implement(builder, countByEnumeratingWithState_objects_count_);
+    builder.addProtocol($protocol);
     return NSFastEnumeration.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -3684,6 +3740,7 @@ interface class NSFastEnumeration extends objc.ObjCProtocolBase {
       bool $keepIsolateAlive = true}) {
     NSFastEnumeration.countByEnumeratingWithState_objects_count_
         .implement(builder, countByEnumeratingWithState_objects_count_);
+    builder.addProtocol($protocol);
   }
 
   /// countByEnumeratingWithState:objects:count:
@@ -4289,6 +4346,10 @@ interface class NSItemProviderReading extends objc.ObjCProtocolBase
         _protocol_NSItemProviderReading);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSItemProviderReading.cast());
+
   /// Builds an object that implements the NSItemProviderReading protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -4298,6 +4359,7 @@ interface class NSItemProviderReading extends objc.ObjCProtocolBase
     final builder =
         objc.ObjCProtocolBuilder(debugName: 'NSItemProviderReading');
 
+    builder.addProtocol($protocol);
     return NSItemProviderReading.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -4307,7 +4369,9 @@ interface class NSItemProviderReading extends objc.ObjCProtocolBase
   ///
   /// Note: You cannot call this method after you have called `builder.build`.
   static void addToBuilder(objc.ObjCProtocolBuilder builder,
-      {bool $keepIsolateAlive = true}) {}
+      {bool $keepIsolateAlive = true}) {
+    builder.addProtocol($protocol);
+  }
 }
 
 enum NSItemProviderRepresentationVisibility {
@@ -4352,6 +4416,10 @@ interface class NSItemProviderWriting extends objc.ObjCProtocolBase
         _protocol_NSItemProviderWriting);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSItemProviderWriting.cast());
+
   /// Builds an object that implements the NSItemProviderWriting protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -4370,6 +4438,7 @@ interface class NSItemProviderWriting extends objc.ObjCProtocolBase
             itemProviderVisibilityForRepresentationWithTypeIdentifier_);
     NSItemProviderWriting.writableTypeIdentifiersForItemProvider
         .implement(builder, writableTypeIdentifiersForItemProvider);
+    builder.addProtocol($protocol);
     return NSItemProviderWriting.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -4389,6 +4458,7 @@ interface class NSItemProviderWriting extends objc.ObjCProtocolBase
             itemProviderVisibilityForRepresentationWithTypeIdentifier_);
     NSItemProviderWriting.writableTypeIdentifiersForItemProvider
         .implement(builder, writableTypeIdentifiersForItemProvider);
+    builder.addProtocol($protocol);
   }
 
   /// itemProviderVisibilityForRepresentationWithTypeIdentifier:
@@ -4811,6 +4881,10 @@ interface class NSMutableCopying extends objc.ObjCProtocolBase {
         obj.ref.pointer, _sel_conformsToProtocol_, _protocol_NSMutableCopying);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSMutableCopying.cast());
+
   /// Builds an object that implements the NSMutableCopying protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -4823,6 +4897,7 @@ interface class NSMutableCopying extends objc.ObjCProtocolBase {
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSMutableCopying');
     NSMutableCopying.mutableCopyWithZone_
         .implement(builder, mutableCopyWithZone_);
+    builder.addProtocol($protocol);
     return NSMutableCopying.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -4837,6 +4912,7 @@ interface class NSMutableCopying extends objc.ObjCProtocolBase {
       bool $keepIsolateAlive = true}) {
     NSMutableCopying.mutableCopyWithZone_
         .implement(builder, mutableCopyWithZone_);
+    builder.addProtocol($protocol);
   }
 
   /// mutableCopyWithZone:
@@ -7538,6 +7614,10 @@ interface class NSObjectProtocol extends objc.ObjCProtocolBase {
         obj.ref.pointer, _sel_conformsToProtocol_, _protocol_NSObject);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSObject.cast());
+
   /// Builds an object that implements the NSObject protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -7596,6 +7676,7 @@ interface class NSObjectProtocol extends objc.ObjCProtocolBase {
     NSObjectProtocol.self$1.implement(builder, self$1);
     NSObjectProtocol.superclass.implement(builder, superclass);
     NSObjectProtocol.zone.implement(builder, zone);
+    builder.addProtocol($protocol);
     return NSObjectProtocol.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -7656,6 +7737,7 @@ interface class NSObjectProtocol extends objc.ObjCProtocolBase {
     NSObjectProtocol.self$1.implement(builder, self$1);
     NSObjectProtocol.superclass.implement(builder, superclass);
     NSObjectProtocol.zone.implement(builder, zone);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSObject protocol. To implement
@@ -7717,6 +7799,7 @@ interface class NSObjectProtocol extends objc.ObjCProtocolBase {
     NSObjectProtocol.self$1.implement(builder, self$1);
     NSObjectProtocol.superclass.implement(builder, superclass);
     NSObjectProtocol.zone.implement(builder, zone);
+    builder.addProtocol($protocol);
     return NSObjectProtocol.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -7778,6 +7861,7 @@ interface class NSObjectProtocol extends objc.ObjCProtocolBase {
     NSObjectProtocol.self$1.implement(builder, self$1);
     NSObjectProtocol.superclass.implement(builder, superclass);
     NSObjectProtocol.zone.implement(builder, zone);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSObject protocol. To implement
@@ -7839,6 +7923,7 @@ interface class NSObjectProtocol extends objc.ObjCProtocolBase {
     NSObjectProtocol.self$1.implement(builder, self$1);
     NSObjectProtocol.superclass.implement(builder, superclass);
     NSObjectProtocol.zone.implement(builder, zone);
+    builder.addProtocol($protocol);
     return NSObjectProtocol.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -7900,6 +7985,7 @@ interface class NSObjectProtocol extends objc.ObjCProtocolBase {
     NSObjectProtocol.self$1.implement(builder, self$1);
     NSObjectProtocol.superclass.implement(builder, superclass);
     NSObjectProtocol.zone.implement(builder, zone);
+    builder.addProtocol($protocol);
   }
 
   /// autorelease
@@ -9075,6 +9161,10 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
         obj.ref.pointer, _sel_conformsToProtocol_, _protocol_NSSecureCoding);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSSecureCoding.cast());
+
   /// Builds an object that implements the NSSecureCoding protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -9087,6 +9177,7 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSSecureCoding');
     NSSecureCoding.encodeWithCoder_.implement(builder, encodeWithCoder_);
     NSSecureCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
     return NSSecureCoding.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -9101,6 +9192,7 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
       bool $keepIsolateAlive = true}) {
     NSSecureCoding.encodeWithCoder_.implement(builder, encodeWithCoder_);
     NSSecureCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSSecureCoding protocol. To implement
@@ -9117,6 +9209,7 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
     NSSecureCoding.encodeWithCoder_
         .implementAsListener(builder, encodeWithCoder_);
     NSSecureCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
     return NSSecureCoding.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -9133,6 +9226,7 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
     NSSecureCoding.encodeWithCoder_
         .implementAsListener(builder, encodeWithCoder_);
     NSSecureCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSSecureCoding protocol. To implement
@@ -9149,6 +9243,7 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
     NSSecureCoding.encodeWithCoder_
         .implementAsBlocking(builder, encodeWithCoder_);
     NSSecureCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
     return NSSecureCoding.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -9165,6 +9260,7 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
     NSSecureCoding.encodeWithCoder_
         .implementAsBlocking(builder, encodeWithCoder_);
     NSSecureCoding.initWithCoder_.implement(builder, initWithCoder_);
+    builder.addProtocol($protocol);
   }
 
   /// encodeWithCoder:
@@ -9568,6 +9664,10 @@ interface class NSStreamDelegate extends objc.ObjCProtocolBase
         obj.ref.pointer, _sel_conformsToProtocol_, _protocol_NSStreamDelegate);
   }
 
+  /// Returns the [objc.Protocol] object for this protocol.
+  static objc.Protocol get $protocol =>
+      objc.Protocol.castFromPointer(_protocol_NSStreamDelegate.cast());
+
   /// Builds an object that implements the NSStreamDelegate protocol. To implement
   /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
   ///
@@ -9579,6 +9679,7 @@ interface class NSStreamDelegate extends objc.ObjCProtocolBase
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSStreamDelegate');
     NSStreamDelegate.stream_handleEvent_
         .implement(builder, stream_handleEvent_);
+    builder.addProtocol($protocol);
     return NSStreamDelegate.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -9592,6 +9693,7 @@ interface class NSStreamDelegate extends objc.ObjCProtocolBase
       bool $keepIsolateAlive = true}) {
     NSStreamDelegate.stream_handleEvent_
         .implement(builder, stream_handleEvent_);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSStreamDelegate protocol. To implement
@@ -9606,6 +9708,7 @@ interface class NSStreamDelegate extends objc.ObjCProtocolBase
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSStreamDelegate');
     NSStreamDelegate.stream_handleEvent_
         .implementAsListener(builder, stream_handleEvent_);
+    builder.addProtocol($protocol);
     return NSStreamDelegate.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -9620,6 +9723,7 @@ interface class NSStreamDelegate extends objc.ObjCProtocolBase
       bool $keepIsolateAlive = true}) {
     NSStreamDelegate.stream_handleEvent_
         .implementAsListener(builder, stream_handleEvent_);
+    builder.addProtocol($protocol);
   }
 
   /// Builds an object that implements the NSStreamDelegate protocol. To implement
@@ -9634,6 +9738,7 @@ interface class NSStreamDelegate extends objc.ObjCProtocolBase
     final builder = objc.ObjCProtocolBuilder(debugName: 'NSStreamDelegate');
     NSStreamDelegate.stream_handleEvent_
         .implementAsBlocking(builder, stream_handleEvent_);
+    builder.addProtocol($protocol);
     return NSStreamDelegate.castFrom(
         builder.build(keepIsolateAlive: $keepIsolateAlive));
   }
@@ -9648,6 +9753,7 @@ interface class NSStreamDelegate extends objc.ObjCProtocolBase
       bool $keepIsolateAlive = true}) {
     NSStreamDelegate.stream_handleEvent_
         .implementAsBlocking(builder, stream_handleEvent_);
+    builder.addProtocol($protocol);
   }
 
   /// stream:handleEvent:
@@ -15854,20 +15960,6 @@ final _objc_msgSend_1i9r4xy = objc.msgSendPointer
     .asFunction<
         void Function(ffi.Pointer<objc.ObjCObject>,
             ffi.Pointer<objc.ObjCSelector>, int)>();
-final _objc_msgSend_1iqj6b6 = objc.msgSendPointer
-    .cast<
-        ffi.NativeFunction<
-            ffi.Void Function(
-                ffi.Pointer<objc.ObjCObject>,
-                ffi.Pointer<objc.ObjCSelector>,
-                ffi.Pointer<objc.ObjCSelector>,
-                ffi.Pointer<ffi.Void>)>>()
-    .asFunction<
-        void Function(
-            ffi.Pointer<objc.ObjCObject>,
-            ffi.Pointer<objc.ObjCSelector>,
-            ffi.Pointer<objc.ObjCSelector>,
-            ffi.Pointer<ffi.Void>)>();
 final _objc_msgSend_1iyq28l = objc.msgSendPointer
     .cast<
         ffi.NativeFunction<
@@ -16184,6 +16276,24 @@ final _objc_msgSend_1qv0eq4 = objc.msgSendPointer
             ffi.Pointer<objc.ObjCSelector>,
             ffi.Pointer<objc.ObjCSelector>,
             ffi.Pointer<objc.ObjCObject>)>();
+final _objc_msgSend_1s2gdyk = objc.msgSendPointer
+    .cast<
+        ffi.NativeFunction<
+            ffi.Void Function(
+                ffi.Pointer<objc.ObjCObject>,
+                ffi.Pointer<objc.ObjCSelector>,
+                ffi.Pointer<objc.ObjCSelector>,
+                ffi.Pointer<ffi.Void>,
+                ffi.Pointer<ffi.Void>,
+                ffi.Pointer<ffi.Char>)>>()
+    .asFunction<
+        void Function(
+            ffi.Pointer<objc.ObjCObject>,
+            ffi.Pointer<objc.ObjCSelector>,
+            ffi.Pointer<objc.ObjCSelector>,
+            ffi.Pointer<ffi.Void>,
+            ffi.Pointer<ffi.Void>,
+            ffi.Pointer<ffi.Char>)>();
 final _objc_msgSend_1sotr3r = objc.msgSendPointer
     .cast<
         ffi.NativeFunction<
@@ -17367,6 +17477,7 @@ late final _sel_addObject_ = objc.registerName("addObject:");
 late final _sel_addObjectsFromArray_ =
     objc.registerName("addObjectsFromArray:");
 late final _sel_addObjects_count_ = objc.registerName("addObjects:count:");
+late final _sel_addProtocol_ = objc.registerName("addProtocol:");
 late final _sel_allKeys = objc.registerName("allKeys");
 late final _sel_allKeysForObject_ = objc.registerName("allKeysForObject:");
 late final _sel_allObjects = objc.registerName("allObjects");
@@ -17401,6 +17512,7 @@ late final _sel_bookmarkDataWithOptions_includingResourceValuesForKeys_relativeT
     objc.registerName(
         "bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error:");
 late final _sel_boolValue = objc.registerName("boolValue");
+late final _sel_buildInstance_ = objc.registerName("buildInstance:");
 late final _sel_bytes = objc.registerName("bytes");
 late final _sel_cStringUsingEncoding_ =
     objc.registerName("cStringUsingEncoding:");
@@ -17606,8 +17718,8 @@ late final _sel_hash = objc.registerName("hash");
 late final _sel_helpAnchor = objc.registerName("helpAnchor");
 late final _sel_host = objc.registerName("host");
 late final _sel_illegalCharacterSet = objc.registerName("illegalCharacterSet");
-late final _sel_implementMethod_withBlock_ =
-    objc.registerName("implementMethod:withBlock:");
+late final _sel_implementMethod_withBlock_withTrampoline_withSignature_ = objc
+    .registerName("implementMethod:withBlock:withTrampoline:withSignature:");
 late final _sel_increaseLengthBy_ = objc.registerName("increaseLengthBy:");
 late final _sel_indexGreaterThanIndex_ =
     objc.registerName("indexGreaterThanIndex:");
@@ -17683,6 +17795,7 @@ late final _sel_initWithCharactersNoCopy_length_freeWhenDone_ =
     objc.registerName("initWithCharactersNoCopy:length:freeWhenDone:");
 late final _sel_initWithCharacters_length_ =
     objc.registerName("initWithCharacters:length:");
+late final _sel_initWithClassName_ = objc.registerName("initWithClassName:");
 late final _sel_initWithCoder_ = objc.registerName("initWithCoder:");
 late final _sel_initWithContentsOfFile_ =
     objc.registerName("initWithContentsOfFile:");
@@ -18033,6 +18146,7 @@ late final _sel_rangeOfString_options_range_locale_ =
     objc.registerName("rangeOfString:options:range:locale:");
 late final _sel_read_maxLength_ = objc.registerName("read:maxLength:");
 late final _sel_recoveryAttempter = objc.registerName("recoveryAttempter");
+late final _sel_registerClass = objc.registerName("registerClass");
 late final _sel_registerObject_visibility_ =
     objc.registerName("registerObject:visibility:");
 late final _sel_registeredTypeIdentifiers =
diff --git a/pkgs/objective_c/lib/src/protocol_builder.dart b/pkgs/objective_c/lib/src/protocol_builder.dart
index fac8bbd839..bbe1b26545 100644
--- a/pkgs/objective_c/lib/src/protocol_builder.dart
+++ b/pkgs/objective_c/lib/src/protocol_builder.dart
@@ -10,24 +10,19 @@ import 'package:ffi/ffi.dart';
 
 import 'c_bindings_generated.dart' as c;
 import 'internal.dart'
-    show
-        FailedToLoadProtocolMethodException,
-        GetProtocolName,
-        ObjCBlockBase,
-        getClass,
-        msgSendPointer,
-        registerName;
+    show FailedToLoadProtocolMethodException, GetProtocolName, ObjCBlockBase;
 import 'objective_c_bindings_generated.dart' as objc;
 import 'selector.dart';
 
 /// Helper class for building Objective C objects that implement protocols.
 class ObjCProtocolBuilder {
-  final _builder = objc.DartProtocolBuilder();
-  final Pointer<c.ObjCObject> _class;
+  final objc.DartProtocolBuilder _builder;
   var _built = false;
 
-  ObjCProtocolBuilder({String? debugName})
-      : _class = _createProtocolClass(debugName);
+  objc.DartProtocolBuilder get builder => _builder;
+
+  ObjCProtocolBuilder({String debugName = 'DOBJCDartProtocol'})
+      : _builder = _createBuilder(debugName);
 
   /// Add a method implementation to the protocol.
   ///
@@ -40,8 +35,8 @@ class ObjCProtocolBuilder {
     if (_built) {
       throw StateError('Protocol is already built');
     }
-    c.addMethod(_class, sel, trampoline, signature);
-    _builder.implementMethod_withBlock_(sel, block.ref.pointer.cast());
+    _builder.implementMethod_withBlock_withTrampoline_withSignature_(
+        sel, block.ref.pointer.cast(), trampoline, signature);
   }
 
   /// Builds the object.
@@ -50,13 +45,9 @@ class ObjCProtocolBuilder {
   /// that all implement the same protocol methods using the same functions.
   objc.NSObject build({bool keepIsolateAlive = true}) {
     if (!_built) {
-      c.registerClassPair(_class);
+      _builder.registerClass();
       _built = true;
     }
-    final obj = objc.DartProtocol.castFromPointer(
-        _msgSendAlloc(_class, _selAlloc),
-        retain: false,
-        release: true);
 
     var disposePort = c.ILLEGAL_PORT;
     if (keepIsolateAlive) {
@@ -64,28 +55,23 @@ class ObjCProtocolBuilder {
       keepAlivePort = RawReceivePort((_) => keepAlivePort.close());
       disposePort = keepAlivePort.sendPort.nativePort;
     }
-    return obj.initDOBJCDartProtocolFromDartProtocolBuilder_withDisposePort_(
-        _builder, disposePort);
+    return _builder.buildInstance_(disposePort);
   }
 
-  static final _msgSendAlloc = msgSendPointer
-      .cast<
-          NativeFunction<
-              Pointer<c.ObjCObject> Function(
-                  Pointer<c.ObjCObject>, Pointer<c.ObjCSelector>)>>()
-      .asFunction<
-          Pointer<c.ObjCObject> Function(
-              Pointer<c.ObjCObject>, Pointer<c.ObjCSelector>)>();
-  static final _selAlloc = registerName('alloc');
-
-  static final _protocolClass = getClass('DOBJCDartProtocol');
+  /// Add the [protocol] to this implementation.
+  ///
+  /// This essentially declares that the implementation implements the protocol.
+  /// There is no automatic check that ensures that the implementation actually
+  /// implements all the methods of the protocol.
+  void addProtocol(objc.Protocol protocol) => _builder.addProtocol_(protocol);
+
   static final _rand = Random();
-  static Pointer<c.ObjCObject> _createProtocolClass(String? debugName) {
-    debugName ??= 'DOBJCDartProtocol';
+  static objc.DartProtocolBuilder _createBuilder(String debugName) {
     final name = '${debugName}_${_rand.nextInt(1 << 32)}'.toNativeUtf8();
-    final cls = c.allocateClassPair(_protocolClass, name.cast(), 0);
+    final builder =
+        objc.DartProtocolBuilder.alloc().initWithClassName_(name.cast());
     calloc.free(name);
-    return cls;
+    return builder;
   }
 }
 
diff --git a/pkgs/objective_c/src/objective_c_runtime.h b/pkgs/objective_c/src/objective_c_runtime.h
index d8f57169cc..f911c549f3 100644
--- a/pkgs/objective_c/src/objective_c_runtime.h
+++ b/pkgs/objective_c/src/objective_c_runtime.h
@@ -9,8 +9,6 @@
 #ifndef OBJECTIVE_C_SRC_OBJECTIVE_C_RUNTIME_H_
 #define OBJECTIVE_C_SRC_OBJECTIVE_C_RUNTIME_H_
 
-#include <stddef.h>
-
 #include "include/dart_api_dl.h"
 
 typedef struct _ObjCSelector ObjCSelector;
@@ -26,11 +24,6 @@ void objc_release(ObjCObject *object);
 ObjCObject *objc_autorelease(ObjCObject *object);
 ObjCObject *object_getClass(ObjCObject *object);
 ObjCObject** objc_copyClassList(unsigned int* count);
-ObjCObject *objc_allocateClassPair(
-    ObjCObject *superclass, const char *name, size_t extraBytes);
-void objc_registerClassPair(ObjCObject *cls);
-bool class_addMethod(
-    ObjCObject *cls, ObjCSelector *name, void *imp, const char *types);
 
 // The signature of this function is just a placeholder. This function is used
 // by every method invocation, and is cast to every signature we need.
diff --git a/pkgs/objective_c/src/protocol.h b/pkgs/objective_c/src/protocol.h
index 085b5048c6..2b3494735f 100644
--- a/pkgs/objective_c/src/protocol.h
+++ b/pkgs/objective_c/src/protocol.h
@@ -9,12 +9,24 @@
 
 #include "include/dart_api_dl.h"
 
+@class DOBJCDartProtocol;
+
+/**
+ * Used by the Dart ObjCProtocolBuilder to construct ObjC classes at runtime to
+ * implement protocols.
+ */
 @interface DOBJCDartProtocolBuilder : NSObject
-+ (instancetype)new;
-- (instancetype)init;
-- (void)implementMethod:(SEL) sel withBlock:(void*)block;
+- (instancetype)initWithClassName: (const char*)name;
+- (void)implementMethod:(SEL)sel withBlock:(void*)block
+    withTrampoline:(void*)trampoline withSignature:(char*)signature;
+- (void)addProtocol:(Protocol*) protocol;
+- (void)registerClass;
+- (DOBJCDartProtocol*)buildInstance: (Dart_Port)port;
 @end
 
+/**
+ * Base class of all classes DOBJCDartProtocolBuilder creates.
+ */
 @interface DOBJCDartProtocol : NSObject
 - (instancetype)initDOBJCDartProtocolFromDartProtocolBuilder:
     (DOBJCDartProtocolBuilder*)builder
diff --git a/pkgs/objective_c/src/protocol.m b/pkgs/objective_c/src/protocol.m
index 1c0d56568c..e163198096 100644
--- a/pkgs/objective_c/src/protocol.m
+++ b/pkgs/objective_c/src/protocol.m
@@ -7,57 +7,66 @@
 #import <Foundation/NSDictionary.h>
 #import <Foundation/NSInvocation.h>
 #import <Foundation/NSValue.h>
+#import <objc/runtime.h>
 
 #if !__has_feature(objc_arc)
 #error "This file must be compiled with ARC enabled"
 #endif
 
 @implementation DOBJCDartProtocolBuilder {
-  NSMutableDictionary *methods;
+  @public NSMutableDictionary *methods;
+  Class clazz;
 }
 
-+ (instancetype)new {
-  return [[self alloc] init];
-}
-
-- (instancetype)init {
-  if (self) {
-    methods = [NSMutableDictionary new];
-  }
+- (instancetype)initWithClassName: (const char*)name {
+  methods = [NSMutableDictionary new];
+  clazz = objc_allocateClassPair([DOBJCDartProtocol class], name, 0);
   return self;
 }
 
-- (void)implement:(SEL)sel withBlock:(id)block {
+- (void)implementMethod:(SEL)sel withBlock:(void*)block
+    withTrampoline:(void*)trampoline withSignature:(char*)signature {
+  class_addMethod(clazz, sel, trampoline, signature);
+  NSValue* key = [NSValue valueWithPointer:sel];
   @synchronized(methods) {
-    [methods setObject:block forKey:[NSValue valueWithPointer:sel]];
+    [methods setObject:(__bridge id)block forKey:key];
   }
 }
 
-- (void)implementMethod:(SEL)sel withBlock:(void*)block {
-  [self implement:sel withBlock:(__bridge id)block];
+- (void)addProtocol:(Protocol*) protocol {
+  class_addProtocol(clazz, protocol);
+}
+
+- (void)registerClass {
+  objc_registerClassPair(clazz);
 }
 
-- (NSDictionary*)copyMethods NS_RETURNS_RETAINED {
-  return [methods copy];
+- (DOBJCDartProtocol*)buildInstance: (Dart_Port)port {
+  DOBJCDartProtocol* inst = [clazz alloc];
+  return [inst initDOBJCDartProtocolFromDartProtocolBuilder:self
+               withDisposePort:port];
 }
+
+- (void)dealloc {
+  objc_disposeClassPair(clazz);
+}
+
 @end
 
 @implementation DOBJCDartProtocol {
-  NSDictionary *methods;
+  DOBJCDartProtocolBuilder* builder;
   Dart_Port dispose_port;
 }
 
 - (id)getDOBJCDartProtocolMethodForSelector:(SEL)sel {
-  return [methods objectForKey:[NSValue valueWithPointer:sel]];
+  return [builder->methods objectForKey:[NSValue valueWithPointer:sel]];
 }
 
 - (instancetype)initDOBJCDartProtocolFromDartProtocolBuilder:
-    (DOBJCDartProtocolBuilder*)builder
+    (DOBJCDartProtocolBuilder*)builder_
     withDisposePort:(Dart_Port)port {
-  if (self) {
-    methods = [builder copyMethods];
-    dispose_port = port;
-  }
+  builder = builder_;
+  dispose_port = port;
   return self;
 }
 


Reviews:
user: login: mraleph
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T10:22:31Z
user: login: liamappelbe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T01:44:40Z
user: login: mraleph
body: LGTM with comments
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-05T09:30:20Z
user: login: liamappelbe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T00:14:21Z
user: login: liamappelbe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T00:14:25Z
user: login: liamappelbe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T00:14:28Z

Review comments:
diff_hunk: @@ -7,22 +7,42 @@
 #import <Foundation/NSDictionary.h>
 #import <Foundation/NSInvocation.h>
 #import <Foundation/NSValue.h>
+#import <objc/runtime.h>
 
 #if !__has_feature(objc_arc)
 #error "This file must be compiled with ARC enabled"
 #endif
 
-@implementation DOBJCDartProtocolBuilder {
-  NSMutableDictionary *methods;
+@interface DOBJCDartProtocolClass : NSObject
path: pkgs/objective_c/src/protocol.m
user: login: mraleph
body: Do you have a specific reason to separate this from the builder itself? 

Consider a simpler setup where `DOBJCDartProtocol` just holds `DOBJCDartProtocolBuilder` and also where we create class in Objective-C and not on the Dart side. Below is a rough sketch.

I also tried to follow [Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.html) which we otherwise seemed to have ignored. It is good to have more comments and somewhat cleaner code.

```objc
@class DOBJCDartProtocol;

/**
 * Class responsible for creating Objective-C classes in runtime.
 * See also `...` in Dart.
 */
@interface DOBJCDartProtocolBuilder : NSObject

/**
 * Initialize the builder using the given @c debugName.
 * 
 * @param debugName prefix to use when creating an Objective-C class,
 *   "DOBJCDartProtocol" will be used if @c debugName is @c nil
 */
- (instancetype)initWithDebugName: (NSString*)debugName;

/**
 * Use @c block to implement selector @c sel.
 * 
 * This method can only be called before the first instance is created
 * via @c newInstanceWithDisposePort.
 * 
 * @param sel Selector which will be implemented.
 * @param block Block to redirect method to.
 */
- (void)implementMethod:(SEL) sel withBlock:(void*)block;

/**
 * Create an instance of the class built by this builder.
 * 
 * Finalizes and registers Objective-C class. No new methods can be implemented
 * after the first instance is created.
 * 
 * @param port Optional port to notify when newly created instance is 
 *   deallocated. Can be @c ILLEGAL_PORT.
 */ 
- (DOBJCDartProtocol*)newInstanceWithDisposePort:(Dart_Port)port;

- (void) dealloc;
@end

@interface DOBJCDartProtocol : NSObject
- (instancetype)initFromBuilder: (DOBJCDartProtocolBuilder*)builder withDisposePort:(Dart_Port)port;

- (id)getBlockImplementingSelector:(SEL)sel;

- (void)dealloc;
@end

@implementation DOBJCDartProtocolBuilder {
  BOOL _finalized;
  Class _cls;
  @public NSMutableDictionary *_methods;
}

static _Atomic int64_t gNextProtocolId = 0;

- (instancetype)initWithDebugName: (NSString *)debugName {
  // Note: no need for if (self) if you don't do `self = [super init]`.
  if (!debugName) {
    debugName = @"DOBJCDartProtocol";
  }
  int64_t id = atomic_fetch_add(&gNextProtocolId, 1);
  debugName = [NSString stringWithFormat:@"%@_%lld",  debugName, id];
  _cls = objc_allocateClassPair(objc_lookUpClass("DOBJCDartProtocol"), [debugName UTF8String], /*extra_bytes=*/0);
  _methods = [NSMutableDictionary new];
  _finalized = NO;
  return self;
}

- (DOBJCDartProtocol*)newInstanceWithDisposePort:(Dart_Port)port {
  if (!_finalized) {
    objc_registerClassPair(_cls);
    _finalized = YES;
  }

  DOBJCDartProtocol* obj = [_cls alloc];
  return [obj initFromBuilder:self withDisposePort:port];
}

- (void) dealloc {
  objc_disposeClassPair(_cls);
}
@end

@implementation DOBJCDartProtocol {
  DOBJCDartProtocolBuilder* _builder;
  Dart_Port _dispose_port;
}

- (instancetype)initFromBuilder: (DOBJCDartProtocolBuilder*)builder withDisposePort:(Dart_Port)port {
  _builder = builder;
  _dispose_port = port;
  return self;
}

- (void)dealloc {
  if (_dispose_port != ILLEGAL_PORT) {
    Dart_PostInteger_DL(_dispose_port, 0);
  }
}

- (id)getBlockImplementingSelector:(SEL)sel {
  return [_builder->_methods objectForKey:[NSValue valueWithPointer:sel]];
}
@end
```


created_at: 2025-03-04T10:20:34Z
author_association: MEMBER
diff_hunk: @@ -7,22 +7,42 @@
 #import <Foundation/NSDictionary.h>
 #import <Foundation/NSInvocation.h>
 #import <Foundation/NSValue.h>
+#import <objc/runtime.h>
 
 #if !__has_feature(objc_arc)
 #error "This file must be compiled with ARC enabled"
 #endif
 
-@implementation DOBJCDartProtocolBuilder {
-  NSMutableDictionary *methods;
+@interface DOBJCDartProtocolClass : NSObject
path: pkgs/objective_c/src/protocol.m
user: login: liamappelbe
body: Done, more or less. In general, all else being equal, I prefer to orchestrate in Dart (because it's a better language ;) ). But yeah, creating the class and instantiating the implementation objects on the ObjC side simplifies things a little, and the `DOBJCDartProtocolClass` is unnecessary.

My formatting is always a little weird because I haven't managed to get clang format to work on these files (or rather, it makes nonsensical formatting decisions, like ignoring like length).

`DOBJCDartProtocol`'s methods (other than dealloc) are given such verbose names, and contain `DOBJC`, to minimize the chances that they collide with a method that the user is trying to implement.
created_at: 2025-03-05T01:44:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,31 +30,42 @@ - (void)implement:(SEL)sel withBlock:(id)block {
   }
 }
 
-- (void)implementMethod:(SEL)sel withBlock:(void*)block {
+- (void)implementMethod:(SEL)sel withBlock:(void*)block
+    withTrampoline:(void*)trampoline withSignature:(char*)signature {
+  class_addMethod(clazz, sel, trampoline, signature);
   [self implement:sel withBlock:(__bridge id)block];
path: pkgs/objective_c/src/protocol.m
user: login: mraleph
body: Should `[self implement]` just be inlined? It is small and not used for anything else.
created_at: 2025-03-05T09:09:43Z
author_association: MEMBER
diff_hunk: @@ -295,8 +295,8 @@ bool _isValidObject(ObjectPtr ptr) {
 
 final _allClasses = <ObjectPtr>{};
 
-bool _isValidClass(ObjectPtr clazz) {
-  if (_allClasses.contains(clazz)) return true;
+bool _isValidClass(ObjectPtr clazz, {bool bypassCache = false}) {
path: pkgs/objective_c/lib/src/internal.dart
user: login: mraleph
body: nit: a more accurate name for `bypassCache` is probably `forceClassListReload` or something like this.
created_at: 2025-03-05T09:13:01Z
author_association: MEMBER
diff_hunk: @@ -33,31 +30,42 @@ - (void)implement:(SEL)sel withBlock:(id)block {
   }
 }
 
-- (void)implementMethod:(SEL)sel withBlock:(void*)block {
+- (void)implementMethod:(SEL)sel withBlock:(void*)block
+    withTrampoline:(void*)trampoline withSignature:(char*)signature {
+  class_addMethod(clazz, sel, trampoline, signature);
   [self implement:sel withBlock:(__bridge id)block];
 }
 
-- (NSDictionary*)copyMethods NS_RETURNS_RETAINED {
-  return [methods copy];
+- (void)registerClass {
path: pkgs/objective_c/src/protocol.m
user: login: mraleph
body: Question: should we be also calling `class_addProtocol` to make code which checks protocol conformance work?

I think right now if somebody asks our protocols dynamically (`[dartProtocol conformsToProtocol:@protocol(Whatever)]`) they will get `NO` even though `dartProtocol` has all necessary methods.
created_at: 2025-03-05T09:22:57Z
author_association: MEMBER
diff_hunk: @@ -7,22 +7,42 @@
 #import <Foundation/NSDictionary.h>
 #import <Foundation/NSInvocation.h>
 #import <Foundation/NSValue.h>
+#import <objc/runtime.h>
 
 #if !__has_feature(objc_arc)
 #error "This file must be compiled with ARC enabled"
 #endif
 
-@implementation DOBJCDartProtocolBuilder {
-  NSMutableDictionary *methods;
+@interface DOBJCDartProtocolClass : NSObject
path: pkgs/objective_c/src/protocol.m
user: login: mraleph
body: Thanks! Choices look reasonable. 

I do also prefer orchestrating in Dart - however I usually apply the rule of "keeping the logic concentrated in one place". So if I am already writing native code and native code will look more natural - then I will shift most of the logic into native code and make Dart code a thin wrapper. This makes the code easier to understand - otherwise the logic gets split and you need to jump back and forth to piece it together. 

> `DOBJCDartProtocol`'s methods (other than dealloc) are given such verbose names, and contain `DOBJC`, to minimize the chances that they collide with a method that the user is trying to implement.

Makes sense. 
created_at: 2025-03-05T09:29:37Z
author_association: MEMBER
diff_hunk: @@ -33,31 +30,42 @@ - (void)implement:(SEL)sel withBlock:(id)block {
   }
 }
 
-- (void)implementMethod:(SEL)sel withBlock:(void*)block {
+- (void)implementMethod:(SEL)sel withBlock:(void*)block
+    withTrampoline:(void*)trampoline withSignature:(char*)signature {
+  class_addMethod(clazz, sel, trampoline, signature);
   [self implement:sel withBlock:(__bridge id)block];
path: pkgs/objective_c/src/protocol.m
user: login: liamappelbe
body: Done
created_at: 2025-03-06T00:14:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -295,8 +295,8 @@ bool _isValidObject(ObjectPtr ptr) {
 
 final _allClasses = <ObjectPtr>{};
 
-bool _isValidClass(ObjectPtr clazz) {
-  if (_allClasses.contains(clazz)) return true;
+bool _isValidClass(ObjectPtr clazz, {bool bypassCache = false}) {
path: pkgs/objective_c/lib/src/internal.dart
user: login: liamappelbe
body: Done
created_at: 2025-03-06T00:14:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,31 +30,42 @@ - (void)implement:(SEL)sel withBlock:(id)block {
   }
 }
 
-- (void)implementMethod:(SEL)sel withBlock:(void*)block {
+- (void)implementMethod:(SEL)sel withBlock:(void*)block
+    withTrampoline:(void*)trampoline withSignature:(char*)signature {
+  class_addMethod(clazz, sel, trampoline, signature);
   [self implement:sel withBlock:(__bridge id)block];
 }
 
-- (NSDictionary*)copyMethods NS_RETURNS_RETAINED {
-  return [methods copy];
+- (void)registerClass {
path: pkgs/objective_c/src/protocol.m
user: login: liamappelbe
body: Done
created_at: 2025-03-06T00:14:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-04T01:04:37Z
body: ## PR Health 


<details>
<summary>
<strong>Breaking changes</strong> :heavy_check_mark:
</summary>

| Package | Change | Current Version | New Version | Needed Version | Looking good? |
| :--- | :--- | ---: | ---: | ---: | ---: |
|objective_c|Breaking|6.0.0|7.0.0-wip|7.0.0|:heavy_check_mark:|



</details>

<details open>
<summary>
<strong>API leaks</strong> :warning:
</summary>

The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.

| Package | Leaked API symbols |
| :--- | :--- |
|objective_c|_Version|


This check can be disabled by tagging the PR with `skip-leaking-check`.
</details>

<details>
<summary>
<strong>License Headers</strong> :heavy_check_mark:
</summary>

```
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
```

| Files |
| :--- |
| _no missing headers_  |

All source files should start with a [license header](https://github.com/dart-lang/ecosystem/wiki/License-Header).

<details>
<summary>
Unrelated files missing license headers
</summary>

| Files |
| :--- |
|pkgs/jni/lib/src/third_party/generated_bindings.dart|
|pkgs/objective_c/lib/src/ns_input_stream.dart|
</details>





</details>


author_association: NONE
user: login: coveralls
created_at: 2025-03-04T02:15:18Z
body: 
[![Coverage Status](https://coveralls.io/builds/72567107/badge)](https://coveralls.io/builds/72567107)

coverage: 88.252% (+0.2%) from 88.062%
when pulling **2ee487c935bdd3e52dc7a1278d0842ea1ddaad36 on proto_polish**
into **6cec7107f2fcea775e29cf9f2d19af6deb787e19 on main**.

author_association: NONE