Skip to content

Commit

Permalink
Add a converter from Dart Stream to NSInputStream (#1555)
Browse files Browse the repository at this point in the history
  • Loading branch information
brianquinlan authored Oct 10, 2024
1 parent d7cdac4 commit 9836208
Show file tree
Hide file tree
Showing 19 changed files with 1,686 additions and 8 deletions.
1 change: 1 addition & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Bump minimum Dart version to 3.4.
- Dedupe `ObjCBlock` trampolines to reduce generated ObjC code.
- Update to latest `package:objective_c`.
- ObjC objects now include the methods from the protocols they implement. Both
required and optional methods are included. Optional methods will throw an
exception if the method isn't implemented.
Expand Down
14 changes: 14 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ class ObjCBuiltInFunctions {
ObjCImport('UnimplementedOptionalMethodException');

// Keep in sync with pkgs/objective_c/ffigen_objc.yaml.

@visibleForTesting
static const builtInInterfaces = {
'DartInputStreamAdapter',
'DartProxy',
'DartProxyBuilder',
'NSArray',
Expand All @@ -58,6 +60,7 @@ class ObjCBuiltInFunctions {
'NSEnumerator',
'NSError',
'NSIndexSet',
'NSInputStream',
'NSInvocation',
'NSItemProvider',
'NSLocale',
Expand All @@ -73,8 +76,11 @@ class ObjCBuiltInFunctions {
'NSNumber',
'NSObject',
'NSOrderedSet',
'NSOutputStream',
'NSProxy',
'NSRunLoop',
'NSSet',
'NSStream',
'NSString',
'NSURL',
'NSURLHandle',
Expand Down Expand Up @@ -104,13 +110,19 @@ class ObjCBuiltInFunctions {
'NSKeyValueSetMutationKind',
'NSOrderedCollectionDifferenceCalculationOptions',
'NSSortOptions',
'NSStreamEvent',
'NSStreamStatus',
'NSStringCompareOptions',
'NSStringEncodingConversionOptions',
'NSStringEnumerationOptions',
'NSURLBookmarkCreationOptions',
'NSURLBookmarkResolutionOptions',
'NSURLHandleStatus',
};
@visibleForTesting
static const builtInProtocols = {
'NSStreamDelegate',
};

// TODO(https://github.com/dart-lang/native/issues/1173): Ideally this check
// would be based on more than just the name.
Expand All @@ -120,6 +132,8 @@ class ObjCBuiltInFunctions {
generateForPackageObjectiveC ? null : builtInCompounds[name];
bool isBuiltInEnum(String name) =>
!generateForPackageObjectiveC && builtInEnums.contains(name);
bool isBuiltInProtocol(String name) =>
!generateForPackageObjectiveC && builtInProtocols.contains(name);
static bool isNSObject(String name) => name == 'NSObject';

// We need to load a separate instance of objc_msgSend for each signature. If
Expand Down
3 changes: 2 additions & 1 deletion pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods {

@override
void sort() => sortMethods();
bool get _isBuiltIn => builtInFunctions.isBuiltInProtocol(originalName);

@override
BindingString toBindingString(Writer w) {
if (!generateBindings) {
if (!generateBindings || _isBuiltIn) {
return const BindingString(
type: BindingStringType.objcProtocol, string: '');
}
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dev_dependencies:
dart_flutter_team_lints: ^2.0.0
json_schema: ^5.1.1
leak_tracker: ^10.0.7
objective_c: ^2.1.0
objective_c: ^3.0.0
test: ^1.16.2

dependency_overrides:
Expand Down
12 changes: 10 additions & 2 deletions pkgs/objective_c/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## 2.1.0-wip

## 3.0.0-wip

- Add the following stream-related types to the core package:
- `NSInputStream`
- `NSOutputStream`
- `NSRunLoop`
- `NSStream`
- `NSStreamDelegate`
- `NSStreamEvent`
- `NSStreamStatus`
- Add `UnimplementedOptionalMethodException`, which is thrown by the ObjC
bindings if an optional method is invoked, and the instance doesn't implement
the method.
Expand Down
14 changes: 14 additions & 0 deletions pkgs/objective_c/ffigen_objc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ output:
headers:
entry-points:
- 'src/foundation.h'
- 'src/input_stream_adapter.h'
- 'src/proxy.h'
ffi-native:
exclude-all-by-default: true
Expand All @@ -22,6 +23,7 @@ external-versions:
objc-interfaces:
# Keep in sync with ffigen's ObjCBuiltInFunctions.builtInInterfaces.
include:
- DartInputStreamAdapter
- DartProxy
- DartProxyBuilder
- NSArray
Expand All @@ -33,6 +35,7 @@ objc-interfaces:
- NSEnumerator
- NSError
- NSIndexSet
- NSInputStream
- NSInvocation
- NSItemProvider
- NSLocale
Expand All @@ -47,14 +50,20 @@ objc-interfaces:
- NSNotification
- NSNumber
- NSObject
- NSOutputStream
- NSOrderedSet
- NSProxy
- NSRunLoop
- NSSet
- NSStream
- NSString
- NSURL
- NSURLHandle
- NSValue
- Protocol
objc-protocols:
include:
- NSStreamDelegate
structs:
include:
- _NSRange
Expand All @@ -79,12 +88,17 @@ enums:
- NSKeyValueSetMutationKind
- NSOrderedCollectionDifferenceCalculationOptions
- NSSortOptions
- NSStreamEvent
- NSStreamStatus
- NSStringCompareOptions
- NSStringEncodingConversionOptions
- NSStringEnumerationOptions
- NSURLBookmarkCreationOptions
- NSURLBookmarkResolutionOptions
- NSURLHandleStatus
globals:
include:
- NSLocalizedDescriptionKey
preamble: |
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
Expand Down
1 change: 1 addition & 0 deletions pkgs/objective_c/ios/Classes/objective_c.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// Relative import to be able to reuse the ObjC sources.
// See the comment in ../objective_c.podspec for more information.
#include "../../src/input_stream_adapter.m"
#include "../../src/objective_c.m"
#include "../../src/objective_c_bindings_generated.m"
#include "../../src/proxy.m"
8 changes: 8 additions & 0 deletions pkgs/objective_c/lib/objective_c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export 'src/internal.dart'
isValidClass,
isValidObject;
export 'src/ns_data.dart';
export 'src/ns_input_stream.dart';
export 'src/ns_mutable_data.dart';
export 'src/ns_string.dart';
// Keep in sync with pkgs/objective_c/ffigen_objc.yaml.
Expand All @@ -45,6 +46,7 @@ export 'src/objective_c_bindings_generated.dart'
NSError,
NSFastEnumerationState,
NSIndexSet,
NSInputStream,
NSInvocation,
NSItemProvider,
NSItemProviderFileOptions,
Expand All @@ -66,10 +68,16 @@ export 'src/objective_c_bindings_generated.dart'
NSObject,
NSOrderedCollectionDifferenceCalculationOptions,
NSOrderedSet,
NSOutputStream,
NSProxy,
NSRange,
NSRunLoop,
NSSet,
NSSortOptions,
NSStream,
NSStreamDelegate,
NSStreamEvent,
NSStreamStatus,
NSString,
NSStringCompareOptions,
NSStringEncodingConversionOptions,
Expand Down
57 changes: 57 additions & 0 deletions pkgs/objective_c/lib/src/ns_input_stream.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';

import 'ns_data.dart';
import 'ns_string.dart';
import 'objective_c_bindings_generated.dart';

extension NSInputStreamStreamExtension on Stream<List<int>> {
/// Return a [NSInputStream] that, when read, will contain the contents of
/// the [Stream].
///
/// > [!IMPORTANT]
/// > [NSInputStream.read_maxLength_] must be called from a different thread
/// > or [Isolate] than the one that calls [toNSInputStream]. Otherwise,
/// > [NSInputStream.read_maxLength_] will deadlock waiting for data to be
/// > added from the [Stream].
NSInputStream toNSInputStream() {
// Eagerly add data until `maxReadAheadSize` is buffered.
const maxReadAheadSize = 4096;

final port = ReceivePort();
final inputStream =
DartInputStreamAdapter.inputStreamWithPort_(port.sendPort.nativePort);
late final StreamSubscription<dynamic> dataSubscription;

dataSubscription = listen((data) {
if (inputStream.addData_(data.toNSData()) > maxReadAheadSize) {
dataSubscription.pause();
}
}, onError: (Object e) {
final d = NSMutableDictionary.new1();
d.setObject_forKey_(e.toString().toNSString(), NSLocalizedDescriptionKey);
inputStream.setError_(NSError.errorWithDomain_code_userInfo_(
'DartError'.toNSString(), 0, d));
port.close();
}, onDone: () {
inputStream.setDone();
port.close();
}, cancelOnError: true);

dataSubscription.pause();
port.listen((count) {
// -1 indicates that the `NSInputStream` is closed. All other values
// indicate that the `NSInputStream` needs more data.
if (count == -1) {
dataSubscription.cancel();
} else {
dataSubscription.resume();
}
}, onDone: () {
dataSubscription.cancel();
});

return inputStream;
}
}
Loading

0 comments on commit 9836208

Please sign in to comment.