Skip to content

Commit a646680

Browse files
authored
[ffigen] Blocking protocol methods (#1870)
1 parent 14368a8 commit a646680

File tree

8 files changed

+143
-5
lines changed

8 files changed

+143
-5
lines changed

pkgs/ffigen/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
is linked with `-dead_strip`.
55
- Handle dart typedefs in import/export of symbol files.
66
- Add support for blocking ObjC blocks that can be invoked from any thread.
7+
- Add support for blocking ObjC protocol methods.
78

89
## 16.0.0
910

pkgs/ffigen/lib/src/code_generator/objc_protocol.dart

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
4949
final buildArgs = <String>[];
5050
final buildImplementations = StringBuffer();
5151
final buildListenerImplementations = StringBuffer();
52+
final buildBlockingImplementations = StringBuffer();
5253
final methodFields = StringBuffer();
5354

5455
final methodNamer = createMethodRenamer(w);
@@ -83,18 +84,25 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
8384
final argsPassed = func.parameters.map((p) => p.name).join(', ');
8485
final wrapper = '($blockFirstArg _, $argsReceived) => func($argsPassed)';
8586

86-
var listenerBuilder = '';
87+
var listenerBuilders = '';
8788
var maybeImplementAsListener = 'implement';
89+
var maybeImplementAsBlocking = 'implement';
8890
if (block.hasListener) {
89-
listenerBuilder = '($funcType func) => $blockUtils.listener($wrapper),';
91+
listenerBuilders = '''
92+
($funcType func) => $blockUtils.listener($wrapper),
93+
($funcType func) => $blockUtils.blocking($wrapper),
94+
''';
9095
maybeImplementAsListener = 'implementAsListener';
96+
maybeImplementAsBlocking = 'implementAsBlocking';
9197
anyListeners = true;
9298
}
9399

94100
buildImplementations.write('''
95101
$name.$fieldName.implement(builder, $argName);''');
96102
buildListenerImplementations.write('''
97103
$name.$fieldName.$maybeImplementAsListener(builder, $argName);''');
104+
buildBlockingImplementations.write('''
105+
$name.$fieldName.$maybeImplementAsBlocking(builder, $argName);''');
98106

99107
methodFields.write(makeDartDoc(method.dartDoc ?? method.originalName));
100108
methodFields.write('''static final $fieldName = $methodClass<$funcType>(
@@ -107,7 +115,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
107115
isInstanceMethod: ${method.isInstanceMethod},
108116
),
109117
($funcType func) => $blockUtils.fromFunction($wrapper),
110-
$listenerBuilder
118+
$listenerBuilders
111119
);
112120
''');
113121
}
@@ -147,6 +155,22 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {
147155
static void addToBuilderAsListener($protocolBuilder builder, $args) {
148156
$buildListenerImplementations
149157
}
158+
159+
/// Builds an object that implements the $originalName protocol. To implement
160+
/// multiple protocols, use [addToBuilder] or [$protocolBuilder] directly. All
161+
/// methods that can be implemented as blocking listeners will be.
162+
static $objectBase implementAsBlocking($args) {
163+
final builder = $protocolBuilder();
164+
$buildBlockingImplementations
165+
return builder.build();
166+
}
167+
168+
/// Adds the implementation of the $originalName protocol to an existing
169+
/// [$protocolBuilder]. All methods that can be implemented as blocking
170+
/// listeners will be.
171+
static void addToBuilderAsBlocking($protocolBuilder builder, $args) {
172+
$buildBlockingImplementations
173+
}
150174
''';
151175
}
152176

pkgs/ffigen/test/native_objc_test/protocol_test.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,69 @@ void main() {
247247
consumer.callMethodOnRandomThread_(protocolImpl);
248248
expect(await listenerCompleter.future, 123);
249249
});
250+
251+
void waitSync(Duration d) {
252+
final t = Stopwatch();
253+
t.start();
254+
while (t.elapsed < d) {
255+
// Waiting...
256+
}
257+
}
258+
259+
test('Method implementation as blocking', () async {
260+
final consumer = ProtocolConsumer.new1();
261+
262+
final listenerCompleter = Completer<int>();
263+
final myProtocol = MyProtocol.implementAsBlocking(
264+
instanceMethod_withDouble_: (NSString s, double x) {
265+
throw UnimplementedError();
266+
},
267+
voidMethod_: (int x) {
268+
listenerCompleter.complete(x);
269+
},
270+
intPtrMethod_: (Pointer<Int32> ptr) {
271+
waitSync(Duration(milliseconds: 100));
272+
ptr.value = 123456;
273+
},
274+
);
275+
276+
// Blocking method.
277+
consumer.callBlockingMethodOnRandomThread_(myProtocol);
278+
expect(await listenerCompleter.future, 123456);
279+
});
280+
281+
test('Multiple protocol implementation as blocking', () async {
282+
final consumer = ProtocolConsumer.new1();
283+
284+
final listenerCompleter = Completer<int>();
285+
final protocolBuilder = ObjCProtocolBuilder();
286+
MyProtocol.addToBuilderAsBlocking(
287+
protocolBuilder,
288+
instanceMethod_withDouble_: (NSString s, double x) {
289+
throw UnimplementedError();
290+
},
291+
voidMethod_: (int x) {
292+
listenerCompleter.complete(x);
293+
},
294+
intPtrMethod_: (Pointer<Int32> ptr) {
295+
waitSync(Duration(milliseconds: 100));
296+
ptr.value = 98765;
297+
},
298+
);
299+
SecondaryProtocol.addToBuilder(protocolBuilder,
300+
otherMethod_b_c_d_: (int a, int b, int c, int d) {
301+
return a * b * c * d;
302+
});
303+
final protocolImpl = protocolBuilder.build();
304+
305+
// Required instance method from secondary protocol.
306+
final otherIntResult = consumer.callOtherMethod_(protocolImpl);
307+
expect(otherIntResult, 24);
308+
309+
// Blocking method.
310+
consumer.callBlockingMethodOnRandomThread_(protocolImpl);
311+
expect(await listenerCompleter.future, 98765);
312+
});
250313
});
251314

252315
group('Manual DartProxy implementation', () {

pkgs/ffigen/test/native_objc_test/protocol_test.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ typedef struct {
4444
- (int32_t)disabledMethod;
4545
#endif
4646

47+
@optional
48+
- (void)intPtrMethod:(int32_t*)ptr;
49+
4750
@end
4851

4952

@@ -70,7 +73,8 @@ typedef struct {
7073
- (NSString*)callInstanceMethod:(id<MyProtocol>)protocol;
7174
- (int32_t)callOptionalMethod:(id<MyProtocol>)protocol;
7275
- (int32_t)callOtherMethod:(id<SecondaryProtocol>)protocol;
73-
- (void)callMethodOnRandomThread:(id<SecondaryProtocol>)protocol;
76+
- (void)callMethodOnRandomThread:(id<MyProtocol>)protocol;
77+
- (void)callBlockingMethodOnRandomThread:(id<MyProtocol>)protocol;
7478
@end
7579

7680

pkgs/ffigen/test/native_objc_test/protocol_test.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ - (void)callMethodOnRandomThread:(id<MyProtocol>)protocol {
3131
[protocol voidMethod:123];
3232
});
3333
}
34+
35+
- (void)callBlockingMethodOnRandomThread:(id<MyProtocol>)protocol {
36+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
37+
int32_t x;
38+
[protocol intPtrMethod:&x];
39+
[protocol voidMethod:x];
40+
});
41+
}
3442
@end
3543

3644

pkgs/objective_c/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- Reduces the chances of duplicate symbols by adding a `DOBJC_` prefix.
55
- Ensure that required symbols are available to FFI even when the final binary
66
is linked with `-dead_strip`.
7+
- Add support for blocking ObjC protocol methods.
78

89
## 4.0.0
910

pkgs/objective_c/lib/src/objective_c_bindings_generated.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6378,6 +6378,26 @@ abstract final class NSStreamDelegate {
63786378
.implementAsListener(builder, stream_handleEvent_);
63796379
}
63806380

6381+
/// Builds an object that implements the NSStreamDelegate protocol. To implement
6382+
/// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly. All
6383+
/// methods that can be implemented as blocking listeners will be.
6384+
static objc.ObjCObjectBase implementAsBlocking(
6385+
{void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) {
6386+
final builder = objc.ObjCProtocolBuilder();
6387+
NSStreamDelegate.stream_handleEvent_
6388+
.implementAsBlocking(builder, stream_handleEvent_);
6389+
return builder.build();
6390+
}
6391+
6392+
/// Adds the implementation of the NSStreamDelegate protocol to an existing
6393+
/// [objc.ObjCProtocolBuilder]. All methods that can be implemented as blocking
6394+
/// listeners will be.
6395+
static void addToBuilderAsBlocking(objc.ObjCProtocolBuilder builder,
6396+
{void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) {
6397+
NSStreamDelegate.stream_handleEvent_
6398+
.implementAsBlocking(builder, stream_handleEvent_);
6399+
}
6400+
63816401
/// stream:handleEvent:
63826402
static final stream_handleEvent_ =
63836403
objc.ObjCProtocolListenableMethod<void Function(NSStream, NSStreamEvent)>(
@@ -6397,6 +6417,10 @@ abstract final class NSStreamDelegate {
63976417
ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.listener(
63986418
(ffi.Pointer<ffi.Void> _, NSStream arg1, NSStreamEvent arg2) =>
63996419
func(arg1, arg2)),
6420+
(void Function(NSStream, NSStreamEvent) func) =>
6421+
ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.blocking(
6422+
(ffi.Pointer<ffi.Void> _, NSStream arg1, NSStreamEvent arg2) =>
6423+
func(arg1, arg2)),
64006424
);
64016425
}
64026426

pkgs/objective_c/lib/src/protocol_builder.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ class ObjCProtocolMethod<T extends Function> {
7575
class ObjCProtocolListenableMethod<T extends Function>
7676
extends ObjCProtocolMethod<T> {
7777
final ObjCBlockBase Function(T) _createListenerBlock;
78+
final ObjCBlockBase Function(T) _createBlockingBlock;
7879

7980
/// Only for use by ffigen bindings.
8081
ObjCProtocolListenableMethod(super._proto, super._sel, super._signature,
81-
super._createBlock, this._createListenerBlock);
82+
super._createBlock, this._createListenerBlock, this._createBlockingBlock);
8283

8384
/// Implement this method on the protocol [builder] as a listener using a Dart
8485
/// [function].
@@ -92,4 +93,16 @@ class ObjCProtocolListenableMethod<T extends Function>
9293
builder.implementMethod(_sel, _sig, _createListenerBlock(function));
9394
}
9495
}
96+
97+
/// Implement this method on the protocol [builder] as a blocking listener
98+
/// using a Dart [function].
99+
///
100+
/// This callback can be invoked from any native thread, and will block the
101+
/// caller until the callback is handled by the Dart isolate that implemented
102+
/// the method. Async functions are not supported.
103+
void implementAsBlocking(ObjCProtocolBuilder builder, T? function) {
104+
if (function != null) {
105+
builder.implementMethod(_sel, _sig, _createBlockingBlock(function));
106+
}
107+
}
95108
}

0 commit comments

Comments
 (0)