toNSInputStream method

NSInputStream toNSInputStream()

Return a NSInputStream that, when read, will contain the contents of the Stream.

Important

NSInputStream.read must be called from a different thread or Isolate than the one that calls toNSInputStream. Otherwise, NSInputStream.read will deadlock waiting for data to be added from the Stream.

Important

NSInputStream.read must be called from a different thread or Isolate than the one that calls toNSInputStream. Otherwise, NSInputStream.read will deadlock waiting for data to be added from the Stream.

Important

toNSInputStream creates a reference cycle between Dart and Objective-C. Unless this cycle is broken, the Isolate calling toNSInputStream will never exit. The cycle can be broken by calling NSInputStream.close or releasing the NSInputStream using NSInputStream.ref.release().

Implementation

NSInputStream toNSInputStream() {
  // Eagerly add data until `maxReadAheadSize` is buffered.
  const maxReadAheadSize = 4096;

  final port = ReceivePort();

  late final DartInputStreamAdapter inputStream;
  late final DartInputStreamAdapterWeakHolder weakInputStream;

  // Only hold a weak reference to the returned `inputStream` so that there is
  // no unbreakable reference cycle between Dart and Objective-C. When the
  // `inputStream`'s `dealloc` method is called then it sends this code a
  // message saying that it was closed.
  autoReleasePool(() {
    inputStream = DartInputStreamAdapter.inputStreamWithPort(
      port.sendPort.nativePort,
    );
    weakInputStream =
        DartInputStreamAdapterWeakHolder.holderWithInputStreamAdapter(
          inputStream,
        );
  });

  late final StreamSubscription<dynamic> dataSubscription;

  dataSubscription = listen(
    (data) {
      final inputStream = weakInputStream.adapter;
      if (inputStream.addData(data.toNSData()) > maxReadAheadSize) {
        dataSubscription.pause();
      }
      inputStream.ref.release();
    },
    onError: (Object e) {
      final inputStream = weakInputStream.adapter;
      final d = NSMutableDictionary();
      d.asDart()[NSLocalizedDescriptionKey] = e.toString().toNSString();
      inputStream.setError(
        NSError.errorWithDomain(
          'DartError'.toNSString(),
          code: 0,
          userInfo: d,
        ),
      );
      inputStream.ref.release();
      port.close();
    },
    onDone: () {
      final inputStream = weakInputStream.adapter;
      inputStream.setDone();
      inputStream.ref.release();
      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 [DartInputStreamAdapter.setError] or
      // [DartInputStreamAdapter.setDone] is called then the close message
      // (-1) will not be sent when the input stream is closed.
      if (count == -1) {
        port.close();
      } else {
        dataSubscription.resume();
      }
    },
    onDone: () {
      dataSubscription.cancel();
    },
  );

  return inputStream;
}