diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md
index 39633bde6c..03c4aa9865 100644
--- a/pkgs/ffigen/CHANGELOG.md
+++ b/pkgs/ffigen/CHANGELOG.md
@@ -4,6 +4,7 @@
   is linked with `-dead_strip`.
 - Handle dart typedefs in import/export of symbol files.
 - Add support for blocking ObjC blocks that can be invoked from any thread.
+- Add support for blocking ObjC protocol methods.
 
 ## 16.0.0
 
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
index 0c0eeb882a..9ccc4c0650 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
@@ -49,6 +49,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
     final buildArgs = <String>[];
     final buildImplementations = StringBuffer();
     final buildListenerImplementations = StringBuffer();
+    final buildBlockingImplementations = StringBuffer();
     final methodFields = StringBuffer();
 
     final methodNamer = createMethodRenamer(w);
@@ -83,11 +84,16 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
       final argsPassed = func.parameters.map((p) => p.name).join(', ');
       final wrapper = '($blockFirstArg _, $argsReceived) => func($argsPassed)';
 
-      var listenerBuilder = '';
+      var listenerBuilders = '';
       var maybeImplementAsListener = 'implement';
+      var maybeImplementAsBlocking = 'implement';
       if (block.hasListener) {
-        listenerBuilder = '($funcType func) => $blockUtils.listener($wrapper),';
+        listenerBuilders = '''
+    ($funcType func) => $blockUtils.listener($wrapper),
+    ($funcType func) => $blockUtils.blocking($wrapper),
+''';
         maybeImplementAsListener = 'implementAsListener';
+        maybeImplementAsBlocking = 'implementAsBlocking';
         anyListeners = true;
       }
 
@@ -95,6 +101,8 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
     $name.$fieldName.implement(builder, $argName);''');
       buildListenerImplementations.write('''
     $name.$fieldName.$maybeImplementAsListener(builder, $argName);''');
+      buildBlockingImplementations.write('''
+    $name.$fieldName.$maybeImplementAsBlocking(builder, $argName);''');
 
       methodFields.write(makeDartDoc(method.dartDoc ?? method.originalName));
       methodFields.write('''static final $fieldName = $methodClass<$funcType>(
@@ -107,7 +115,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
           isInstanceMethod: ${method.isInstanceMethod},
       ),
       ($funcType func) => $blockUtils.fromFunction($wrapper),
-      $listenerBuilder
+      $listenerBuilders
     );
 ''');
     }
@@ -147,6 +155,22 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
   static void addToBuilderAsListener($protocolBuilder builder, $args) {
     $buildListenerImplementations
   }
+
+  /// Builds an object that implements the $originalName protocol. To implement
+  /// multiple protocols, use [addToBuilder] or [$protocolBuilder] directly. All
+  /// methods that can be implemented as blocking listeners will be.
+  static $objectBase implementAsBlocking($args) {
+    final builder = $protocolBuilder();
+    $buildBlockingImplementations
+    return builder.build();
+  }
+
+  /// Adds the implementation of the $originalName protocol to an existing
+  /// [$protocolBuilder]. All methods that can be implemented as blocking
+  /// listeners will be.
+  static void addToBuilderAsBlocking($protocolBuilder builder, $args) {
+    $buildBlockingImplementations
+  }
 ''';
     }
 
diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.dart b/pkgs/ffigen/test/native_objc_test/protocol_test.dart
index d6176506e8..cdc9bef334 100644
--- a/pkgs/ffigen/test/native_objc_test/protocol_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/protocol_test.dart
@@ -247,6 +247,69 @@ void main() {
         consumer.callMethodOnRandomThread_(protocolImpl);
         expect(await listenerCompleter.future, 123);
       });
+
+      void waitSync(Duration d) {
+        final t = Stopwatch();
+        t.start();
+        while (t.elapsed < d) {
+          // Waiting...
+        }
+      }
+
+      test('Method implementation as blocking', () async {
+        final consumer = ProtocolConsumer.new1();
+
+        final listenerCompleter = Completer<int>();
+        final myProtocol = MyProtocol.implementAsBlocking(
+          instanceMethod_withDouble_: (NSString s, double x) {
+            throw UnimplementedError();
+          },
+          voidMethod_: (int x) {
+            listenerCompleter.complete(x);
+          },
+          intPtrMethod_: (Pointer<Int32> ptr) {
+            waitSync(Duration(milliseconds: 100));
+            ptr.value = 123456;
+          },
+        );
+
+        // Blocking method.
+        consumer.callBlockingMethodOnRandomThread_(myProtocol);
+        expect(await listenerCompleter.future, 123456);
+      });
+
+      test('Multiple protocol implementation as blocking', () async {
+        final consumer = ProtocolConsumer.new1();
+
+        final listenerCompleter = Completer<int>();
+        final protocolBuilder = ObjCProtocolBuilder();
+        MyProtocol.addToBuilderAsBlocking(
+          protocolBuilder,
+          instanceMethod_withDouble_: (NSString s, double x) {
+            throw UnimplementedError();
+          },
+          voidMethod_: (int x) {
+            listenerCompleter.complete(x);
+          },
+          intPtrMethod_: (Pointer<Int32> ptr) {
+            waitSync(Duration(milliseconds: 100));
+            ptr.value = 98765;
+          },
+        );
+        SecondaryProtocol.addToBuilder(protocolBuilder,
+            otherMethod_b_c_d_: (int a, int b, int c, int d) {
+          return a * b * c * d;
+        });
+        final protocolImpl = protocolBuilder.build();
+
+        // Required instance method from secondary protocol.
+        final otherIntResult = consumer.callOtherMethod_(protocolImpl);
+        expect(otherIntResult, 24);
+
+        // Blocking method.
+        consumer.callBlockingMethodOnRandomThread_(protocolImpl);
+        expect(await listenerCompleter.future, 98765);
+      });
     });
 
     group('Manual DartProxy implementation', () {
diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.h b/pkgs/ffigen/test/native_objc_test/protocol_test.h
index 21262a248e..dc3f07dfd4 100644
--- a/pkgs/ffigen/test/native_objc_test/protocol_test.h
+++ b/pkgs/ffigen/test/native_objc_test/protocol_test.h
@@ -44,6 +44,9 @@ typedef struct {
 - (int32_t)disabledMethod;
 #endif
 
+@optional
+- (void)intPtrMethod:(int32_t*)ptr;
+
 @end
 
 
@@ -70,7 +73,8 @@ typedef struct {
 - (NSString*)callInstanceMethod:(id<MyProtocol>)protocol;
 - (int32_t)callOptionalMethod:(id<MyProtocol>)protocol;
 - (int32_t)callOtherMethod:(id<SecondaryProtocol>)protocol;
-- (void)callMethodOnRandomThread:(id<SecondaryProtocol>)protocol;
+- (void)callMethodOnRandomThread:(id<MyProtocol>)protocol;
+- (void)callBlockingMethodOnRandomThread:(id<MyProtocol>)protocol;
 @end
 
 
diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.m b/pkgs/ffigen/test/native_objc_test/protocol_test.m
index 91b7a7f406..975effc685 100644
--- a/pkgs/ffigen/test/native_objc_test/protocol_test.m
+++ b/pkgs/ffigen/test/native_objc_test/protocol_test.m
@@ -31,6 +31,14 @@ - (void)callMethodOnRandomThread:(id<MyProtocol>)protocol {
     [protocol voidMethod:123];
   });
 }
+
+- (void)callBlockingMethodOnRandomThread:(id<MyProtocol>)protocol {
+  dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
+    int32_t x;
+    [protocol intPtrMethod:&x];
+    [protocol voidMethod:x];
+  });
+}
 @end
 
 
diff --git a/pkgs/objective_c/CHANGELOG.md b/pkgs/objective_c/CHANGELOG.md
index 1c11f17ce2..69b46c53e6 100644
--- a/pkgs/objective_c/CHANGELOG.md
+++ b/pkgs/objective_c/CHANGELOG.md
@@ -4,6 +4,7 @@
 - Reduces the chances of duplicate symbols by adding a `DOBJC_` prefix.
 - Ensure that required symbols are available to FFI even when the final binary
   is linked with `-dead_strip`.
+- Add support for blocking ObjC protocol methods.
 - Add various ObjC categories (extension methods) to the built in classes.
 
 ## 4.0.0
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 a863032fd2..352dde84fd 100644
--- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart
+++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart
@@ -7439,6 +7439,26 @@ abstract final class NSStreamDelegate {
         .implementAsListener(builder, stream_handleEvent_);
   }
 
+  /// Builds an object that implements the NSStreamDelegate protocol. To implement
+  /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly. All
+  /// methods that can be implemented as blocking listeners will be.
+  static objc.ObjCObjectBase implementAsBlocking(
+      {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) {
+    final builder = objc.ObjCProtocolBuilder();
+    NSStreamDelegate.stream_handleEvent_
+        .implementAsBlocking(builder, stream_handleEvent_);
+    return builder.build();
+  }
+
+  /// Adds the implementation of the NSStreamDelegate protocol to an existing
+  /// [objc.ObjCProtocolBuilder]. All methods that can be implemented as blocking
+  /// listeners will be.
+  static void addToBuilderAsBlocking(objc.ObjCProtocolBuilder builder,
+      {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) {
+    NSStreamDelegate.stream_handleEvent_
+        .implementAsBlocking(builder, stream_handleEvent_);
+  }
+
   /// stream:handleEvent:
   static final stream_handleEvent_ =
       objc.ObjCProtocolListenableMethod<void Function(NSStream, NSStreamEvent)>(
@@ -7458,6 +7478,10 @@ abstract final class NSStreamDelegate {
         ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.listener(
             (ffi.Pointer<ffi.Void> _, NSStream arg1, NSStreamEvent arg2) =>
                 func(arg1, arg2)),
+    (void Function(NSStream, NSStreamEvent) func) =>
+        ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.blocking(
+            (ffi.Pointer<ffi.Void> _, NSStream arg1, NSStreamEvent arg2) =>
+                func(arg1, arg2)),
   );
 }
 
diff --git a/pkgs/objective_c/lib/src/protocol_builder.dart b/pkgs/objective_c/lib/src/protocol_builder.dart
index c70a12b8f7..d9fbaa9ee3 100644
--- a/pkgs/objective_c/lib/src/protocol_builder.dart
+++ b/pkgs/objective_c/lib/src/protocol_builder.dart
@@ -75,10 +75,11 @@ class ObjCProtocolMethod<T extends Function> {
 class ObjCProtocolListenableMethod<T extends Function>
     extends ObjCProtocolMethod<T> {
   final ObjCBlockBase Function(T) _createListenerBlock;
+  final ObjCBlockBase Function(T) _createBlockingBlock;
 
   /// Only for use by ffigen bindings.
   ObjCProtocolListenableMethod(super._proto, super._sel, super._signature,
-      super._createBlock, this._createListenerBlock);
+      super._createBlock, this._createListenerBlock, this._createBlockingBlock);
 
   /// Implement this method on the protocol [builder] as a listener using a Dart
   /// [function].
@@ -92,4 +93,16 @@ class ObjCProtocolListenableMethod<T extends Function>
       builder.implementMethod(_sel, _sig, _createListenerBlock(function));
     }
   }
+
+  /// Implement this method on the protocol [builder] as a blocking listener
+  /// using a Dart [function].
+  ///
+  /// This callback can be invoked from any native thread, and will block the
+  /// caller until the callback is handled by the Dart isolate that implemented
+  /// the method. Async functions are not supported.
+  void implementAsBlocking(ObjCProtocolBuilder builder, T? function) {
+    if (function != null) {
+      builder.implementMethod(_sel, _sig, _createBlockingBlock(function));
+    }
+  }
 }