0

I am creating an NSOutputStream and passing it to an NSOperation, which I call from an NSOperationQueue. In the operation, I am polling for hasSpaceAvailable so that I can write out to the socket. This works fine the first time I write out to the socket. After the operation returns, however, and I try to write again to the socket, the write never goes through as I'm infinitely waiting for space to become available in the output socket. I've tried opening/closing the output stream each time I write, but have the same problem.

I open the output stream in the init function (the NSOutputStream is created from a Bluetooth EASession:

_commandSession = [[EASession alloc] initWithAccessory:self.selectedAccessory forProtocol:commandProtocolString];
_commandOutputStream = [_commandSession outputStream];
[_commandOutputStream open];

I also create the operation queue in the init:

_senderOperationQueue = [[NSOperationQueue alloc] init];
_senderOperationQueue.name = @"Send Queue";
_senderOperationQueue.maxConcurrentOperationCount = 1;

I have a text field with the data I want to send over the output stream. This function is called each time I click the send button:

-(void)sendCommandData:(NSData *)buf
{
  _commandSendOperation =[[SenderOperation alloc] initWithStream:_commandOutputStream data:buf delegate:self];
  [_senderOperationQueue addOperation:_commandSendOperation];
}

This is how my operation code looks like: (SenderOperation.h)

#import <Foundation/Foundation.h>

@protocol SenderOperationDelegate;

@interface SenderOperation : NSOperation
{
    NSOutputStream *_stream;
    NSData *_sendData;
}

@property (nonatomic, assign) id <SenderOperationDelegate> delegate;
@property (nonatomic, retain) NSOutputStream *stream;
@property (nonatomic, retain) NSData *sendData;

- (id)initWithStream:(NSOutputStream *)stream data:(NSData *)buf delegate:(id<SenderOperationDelegate>)theDelegate;

@end

// Delegate to notify main thread the completion of a BT input buffer stream
@protocol SenderOperationDelegate <NSObject>
-(void)sendComplete:(SenderOperation *)sender;
@end

(SenderOperation.m)

#import "SenderOperation.h"

@implementation SenderOperation

@synthesize delegate = _delegate;
@synthesize stream = _stream;
@synthesize sendData = _sendData;

- (id)initWithStream:(NSOutputStream *)stream data:(NSData *)buf delegate:(id<SenderOperationDelegate>)theDelegate
{
  if (self = [super init])
  {
    self.delegate = theDelegate;
    self.stream = stream;
    self.sendData = buf;
  }
  return self;
}

#define MAX_PACKET_SIZE 20

- (void)main
{
  if (self.isCancelled)
    return;

// total length of the data packet we need to send
int totalLength = [_sendData length];

// length of packet to send (given our upper bound)
int len = (totalLength <= MAX_PACKET_SIZE) ? totalLength:MAX_PACKET_SIZE;

// stream write response
int streamWriteResponse;

// bytes already written out to the output stream
int bytesWritten = 0;

while (1)
{
 if (!len) break;

 if ([self.stream hasSpaceAvailable])
 {
   streamWriteResponse = [self.stream write:[self.sendData bytes] maxLength:len];

   if (streamWriteResponse == -1)
   {
    break;
   }

  bytesWritten += streamWriteResponse;

  // update the data buffer with left over data that needs to be written
  if (totalLength - bytesWritten)
    self.sendData = [self.sendData subdataWithRange:NSMakeRange(bytesWritten, totalLength - bytesWritten)];

  // update length of next data packet write
  len = (totalLength - bytesWritten) >= MAX_PACKET_SIZE ? MAX_PACKET_SIZE : (totalLength - bytesWritten);
  }
}

[(NSObject *)self.delegate performSelectorOnMainThread:@selector(sendComplete:) withObject:self waitUntilDone:NO];
}
Vineet Singh
  • 4,009
  • 1
  • 28
  • 39
ayl
  • 384
  • 1
  • 5
  • 14

1 Answers1

0

You'll need to use run-loop scheduling for your stream, rather than polling. Quoth the docs for -[EASession outputStream]:

This stream is provided for you automatically by the session object but you must configure it if you want to receive any associated stream events. You do this by assigning a delegate object to the stream that implements the stream:handleEvent: delegate method. You must then schedule the stream in a run loop so that it can send data asynchronously from one of your application’s threads.

jatoben
  • 3,079
  • 15
  • 12
  • 3
    I disagree, this article says you can either use polling or run-loop scheduling. Polling worked 100% for me on 5.1 but on 6.1 devices the output stream never has space available. https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Streams/Articles/PollingVersusRunloop.html – Wayne Uroda Apr 11 '13 at 01:51
  • Does the same thing happens when you try to get a new outputStream from the socket instead of closing and opening it again? Apple says: (from Apple docs's NSStream Reference, instance method -open:) *"A stream must be created before it can be opened. Once opened, a stream cannot be closed and reopened."* – Gerald Eersteling Oct 07 '13 at 14:31