I am using NSInputStream to grab some resources from web like this:
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
CFRelease(request);
_inputStream = CFBridgingRelease(readStream);
_inputStream.delegate = self;
[_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
And then to offload the actual work to other thread:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
NSLog(#"stream: %# handleEvent: %d", aStream, eventCode);
dispatch_async(_custom_serial_queue, ^{
switch(eventCode) {
case NSStreamEventOpenCompleted: {
NSLog(#"Stream has opened");
break;
}
// rest of switch
});
}
I see Stream has opened log and then in 95% of cases my main thread freezes
This happens only on iPhone4 and iOS6.1, on iPhone5s and iOS7 and iPad3 iOS6.1 everything is fine.
Could you please suggest what is wrong with this code.
EDIT 1: I have tracked down problem to some piece of code(in 3rd party lib) which uses __sync_add_and_fetch function. It seems it causes the issue but I still need to use it.
EDIT 2: I have changed __sync_add_and_fetch function by OSAtomicAdd32 but it hasn't changed anything.
EDIT 3: I have checked the problem on other devices. Works fine on iPhone4s iOS7, iPad2 iOS6.1. It seems the problem is only relevant to 1 core devices.
Related
I have to say sorry before, my english's not good.
I'm working on a project related to "external accessory framework".
I need to fetch some data stream from our product through a "USB to lightning" adapter.
I've been spinning my wheel for several days.
My question here is "what's the exact way to use the framework".
I found something on apple dev forum:
pic
Does it mean that if i have a "USB to lightning" adapter and its protocol string(For now, we're not going to make our own adapter, we plan to work with another company), then I plug our product to it, my app would be notified of the attachment or removal of the accessory.
Am i right?
I'm confusing right now, cause a colleague of mine contacted a adapter vendor, and they told her there is NO such thing called "protocol string".
I guess for some reason they just don't want us to know.
thanks!
You have set up these notifications for connection. Make you make connection through BT or USB.
/* Setup notification monitor */
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleAccessoryConnectNotification:) name:EAAccessoryDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleAccessoryDisconnectNotification:) name:EAAccessoryDidDisconnectNotification object:nil];
/* Turn on EA notifications */
[[EAAccessoryManager sharedAccessoryManager] registerForLocalNotifications];
Once connection establishes you will get a call back with accessory info.
- (void) handleAccessoryConnectNotification:(NSNotification*)notification
{
NSDictonary *earInfo = notification.userInfo;
Print(#“Accessory Info: %#”,earInfo);
// Get connected Accessory object.
EAAccessory* currentAccessory = eaInfo[#"EAAccessoryKey"];
// Create Session with base Protocol with Accessory.
EASession* currentEASession = [[EASession alloc] initWithAccessory:currentAccessory forProtocol:“com.accessory.base”];
/* Open input and output steam */
if ( self.currentEASession.inputStream && self.currentEASession.outputStream)
{
[[currentEASession inputStream] setDelegate:self];
[[currentEASession inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[currentEASession inputStream] open];
[[currentEASession outputStream] setDelegate:self];
[[currentEASession outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[currentEASession outputStream] open];
}
}
- (void)handleAccessoryDisconnectNotification:(NSNotification*)notification {
Print(#“Accessory Info: %#”notification.userInfo);
}
# Read and Write data delegate method
- ( void ) stream: ( NSStream * ) aStream handleEvent: ( NSStreamEvent ) eventCode {
#try
{
switch ( eventCode )
{
case NSStreamEventOpenCompleted:{
Print(#“[%s] NSStreamEventOpenCompleted",__PRETTY_FUNCTION__);
break;
}
case NSStreamEventHasSpaceAvailable:
{
/* Write data */
break;
}
case NSStreamEventHasBytesAvailable:
{
//TODO
/* Read data */
static uint8_t buffer[65536];
NSInteger n = 65536;
while (n == 65536)
{
n = [self.currentEASession.inputStream read:buffer maxLength:65536];
}
break;
}
case NSStreamEventErrorOccurred:
{
//TODO
Print(#"[%s] NSStreamEventErrorOccurred",__PRETTY_FUNCTION__);
break;
}
case NSStreamEventEndEncountered:
{
//TODO
Print(#"[%s] NSStreamEventEndEncountered",__PRETTY_FUNCTION__);
break;
}
default:
{
break;
}
}
}
#catch (NSException *exception)
{
}
}
Relation between external accessory and protocol is.
When you connect iOS device with accessory. You have to create a session between accessory and iOS device to read and write data. Protocol string will identifier unique connection between accessory and iOS device for sharing and writing data.
Same time one accessory can be connected with multiple iOS devices. Each device will have a unique connection or session in between and it will identify through protocol.
My app uses NSInputStream like below:
inputStream.delegate = self;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[readStream open];
and delegate:
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
It works fine, but all other requests that i do, it queued until first is finished.
I can do one per time and there is no way to do multiple concurrent requests.
There is a solution ?
Thank you
This solution not work for me :
https://stackoverflow.com/a/15346292/1376961
UPDATE:
Was my server can't handle multiple connections from the same source.
You will need to create your streams in separate threads to enable them to work simultaneously. I assume you have a method that sets up the inputStream you referred to:
- (void)openStreamInNewThread {
[NSThread detachNewThreadSelector:#selector(openStream) toTarget:self withObject:nil];
}
- (void)openStream {
NSInputStream *inputStream;
// stream setup
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
}
Note that [NSRunLoop currentRunLoop] will return the runloop of the current thread. So you have the newly created stream running in a separate thread loading data simultaneously with other streams in their own threads.
You can try to schedule each stream in its own run loop. Below is a refined method from the mock class designed to unit-test my POSInputStreamLibrary:
static const NSTimeInterval kRunLoopCycleInterval = 0.01f;
static const uint64_t kDispatchDeltaNanoSec = 250000000;
- (POSRunLoopResult)launchNSRunLoopWithStream:(NSInputStream *)stream delegate:(id<NSStreamDelegate>)streamDelegate {
stream.delegate = streamDelegate;
__block BOOL breakRunLoop = NO;
__block dispatch_semaphore_t doneSemaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[stream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
if ([stream streamStatus] == NSStreamStatusNotOpen) {
NSLog(#"%#: opening stream...", [NSThread currentThread]);
[stream open];
}
while ([runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopCycleInterval]] && !breakRunLoop)
{}
NSLog(#"%#: We are done!", [NSThread currentThread]);
dispatch_semaphore_signal(doneSemaphore);
});
POSRunLoopResult result = dispatch_semaphore_wait(doneSemaphore, dispatch_time(DISPATCH_TIME_NOW, kDispatchDeltaNanoSec)) == 0 ? POSRunLoopResultDone : POSRunLoopResultTimeout;
if (POSRunLoopResultTimeout == result) {
breakRunLoop = YES;
dispatch_semaphore_wait(doneSemaphore, DISPATCH_TIME_FOREVER);
}
return result;
}
Each time I create a new NSInputStream, I add it to a block object, and then store the block object in an NSMutableArray.
I posted code that streams video from one iOS to another:
https://app.box.com/s/94dcm9qjk8giuar08305qspdbe0pc784
Build this app with Xcode 11; run it on two iOS 11 devices.
Touch the Camera icon on one of the two devices to start streaming live video.
If you don't have two devices, you can run the app in a simulator; however, stream from the real device only (the camera is not available on the simulator).
I am using a FTP library for iOS (nkreipke/FTPManager). It works great. I can download a list of directories, upload images, etc.
The problem is that if you leave the app open for like 5 minutes doing nothing and then you try to download or upload, nothing happens.
I've been debugging it and found out that the NSStreamDelegate method - (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent never gets called after that short period of time of being inactive.
Here is the code:
- (NSArray*) _contentsOfServer:(FMServer*)server {
BOOL success = YES;
action = _FMCurrentActionContentsOfServer;
fileSize = 0;
self.directoryListingData = [[NSMutableData alloc] init];
NSURL* dest = [server.destination ftpURLForPort:server.port];
And(success, (dest != nil));
Check(success);
if (![dest.absoluteString hasSuffix:#"/"]) {
//if the url does not end with an '/' the method fails.
//no problem, we can fix this.
dest = [NSURL URLWithString:[NSString stringWithFormat:#"%#/",dest.absoluteString]];
}
CFReadStreamRef readStream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef)dest);
And(success, (readStream != NULL));
if (!success) return nil;
self.serverReadStream = (__bridge_transfer NSInputStream*) readStream;
And(success, [self.serverReadStream setProperty:server.username forKey:(id)kCFStreamPropertyFTPUserName]);
And(success, [self.serverReadStream setProperty:server.password forKey:(id)kCFStreamPropertyFTPPassword]);
if (!success) return nil;
self.bufferOffset = 0;
self.bufferLimit = 0;
currentRunLoop = CFRunLoopGetCurrent();
self.serverReadStream.delegate = self;
[self.serverReadStream open];
[self.serverReadStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CFRunLoopRun(); //<- Hangs here.
And(success, streamSuccess);
if (!success) return nil;
NSArray* directoryContents = [self _createListingArrayFromDirectoryListingData:self.directoryListingData];
self.directoryListingData = nil;
return directoryContents;
serverReadStream is a NSInputStream object.
When the action ends:
if (self.serverReadStream) {
[self.serverReadStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CFRunLoopStop(CFRunLoopGetCurrent());
self.serverReadStream.delegate = nil;
[self.serverReadStream close];
self.serverReadStream = nil;
}
This code is located at FTPManager.m. I've been looking for answers around the internet but couldn't find any. I don't know why the NSStreamDelegate method gets called when used constantly but after some time of inactivity, it doesn't get called.
It would be nice if someone could help me.
Thanks.
I'm using the same library in my iPad application and I was stuck in the same CFRunLoopRun() call when I was trying to refresh the FTP server content when resuming my application.
I had to do two changes to the FTPManager code.
First, I've changed the order of
[self.serverReadStream open];
[self.serverReadStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
to
[self.serverReadStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.serverReadStream open];
The reason why is that the stream cannot by opened. An error event is raised during the call to open but if the stream is not schedule to the run loop, we will never receive the error event. So scheduling before opening will allow us to get the error event.
The other change which was needed in my case was to disable the persistent connection. It seems that after a suspend/resume cycle of the device, the internal socket used by the stream is getting staled and is always returning an error when trying to access it.
Adding the following line in the stream configuration has solve the issue on my side :
[self.serverReadStream setProperty: (id) kCFBooleanFalse forKey: (id) kCFStreamPropertyFTPAttemptPersistentConnection];
Hope this helps.
(UPDATED) this is the problem in a nutshell: in iOS I want to read a large file, do some processing on it (in this particular case encode as Base64 string() and save to a temp file on the device. I set up an NSInputStream to read from a file, then in
(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
I'm doing most of the work. For some reason, sometimes I can see the NSInputStream just stops working. I know because I have a line
NSLog(#"stream %# got event %x", stream, (unsigned)eventCode);
in the beginning of (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode and sometimes I would just see the output
stream <__NSCFInputStream: 0x1f020b00> got event 2
(which corresponds to the event NSStreamEventHasBytesAvailable) and then nothing afterwards. Not event 10, which corresponds to NSStreamEventEndEncountered, not an error event, nothing! And also sometimes I even get a EXC_BAD_ACCESS exception which I have no idea at the moment how to debug. Any help would be appreciated.
Here is the implementation. Everything starts when I hit a "submit" button, which triggers:
- (IBAction)submit:(id)sender {
[p_spinner startAnimating];
[self performSelector: #selector(sendData)
withObject: nil
afterDelay: 0];
}
Here is sendData:
-(void)sendData{
...
_tempFilePath = ... ;
[[NSFileManager defaultManager] createFileAtPath:_tempFilePath contents:nil attributes:nil];
[self setUpStreamsForInputFile: [self.p_mediaURL path] outputFile:_tempFilePath];
[p_spinner stopAnimating];
//Pop back to previous VC
[self.navigationController popViewControllerAnimated:NO] ;
}
Here is setUpStreamsForInputFile called above:
- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
[p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[p_iStream open];
}
Finally, this is where most logic occurs:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSLog(#"stream %# got event %x", stream, (unsigned)eventCode);
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if (stream == self.p_iStream){
if(!_tempMutableData) {
_tempMutableData = [NSMutableData data];
}
if ([_streamdata length]==0){ //we want to write to the buffer only when it has been emptied by the output stream
unsigned int buffer_len = 24000;//read in chunks of 24000
uint8_t buf[buffer_len];
unsigned int len = 0;
len = [p_iStream read:buf maxLength:buffer_len];
if(len) {
[_tempMutableData appendBytes:(const void *)buf length:len];
NSString* base64encData = [Base64 encodeBase64WithData:_tempMutableData];
_streamdata = [base64encData dataUsingEncoding:NSUTF8StringEncoding]; //encode the data as Base64 string
[_tempFileHandle writeData:_streamdata];//write the data
[_tempFileHandle seekToEndOfFile];// and move to the end
_tempMutableData = [NSMutableData data]; //reset mutable data buffer
_streamdata = [[NSData alloc] init]; //release the data buffer
}
}
}
break;
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
stream = nil;
//do some more stuff here...
...
break;
}
case NSStreamEventHasSpaceAvailable:
case NSStreamEventOpenCompleted:
case NSStreamEventNone:
{
...
}
}
case NSStreamEventErrorOccurred:{
...
}
}
}
Note: when I posted this first, I was under a wrong impression the issue had something to do with using GCD. As per Rob's answer below I removed the GCD code and the issue persists.
First: in your original code, you were not using a background thread, but the main thread (dispatch_async but on the main queue).
When you schedule NSInputStream to run on the default runloop (so, the runloop of the main thread), the events are received when the main thread is in the default mode (NSDefaultRunLoopMode).
But: if you check, the default runloop changes mode in some situations (for example, during an UIScrollView scroll and some other UI updates). When the main runloop is in a mode different than the NSDefaultRunLoopMode, your events are not received.
Your old code, with the dispatch_async, was almost good (but move the UI Updates on the main thread). You have to add only few changes:
dispatch in the background, with something like this:
:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^{
// your background code
//end of your code
[[NSRunLoop currentRunLoop] run]; // start a run loop, look at the next point
});
start a run loop on that thread. This must be done at the end (last line) of the dispatch async call, with this code
:
[[NSRunLoop currentRunLoop] run]; // note: this method never returns, so it must be THE LAST LINE of your dispatch
Try and let me know
EDIT - added example code:
To be more clear, I copy-paste your original code updated:
- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
// here: change the queue type and use a background queue (you can change priority)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^ {
[p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[p_iStream open];
// here: start the loop
[[NSRunLoop currentRunLoop] run];
// note: all code below this line won't be executed, because the above method NEVER returns.
});
}
After making this modification, your:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {}
method, will be called on the same thread where you started the run loop, a background thread: if you need to update the UI, it's important that you dispatch again to the main thread.
Extra informations:
In my code I use dispatch_async on a random background queue (which dispatch your code on one of the available background threads, or start a new one if needed, all "automagically"). If you prefer, you can start your own thread instead of using a dispatch async.
Moreover, I don't check if a runloop is already running before sending the "run" message (but you can check it using the currentMode method, look NSRunLoop reference for more informations). It shouldn't be necessary because each thread has only one associated NSRunLoop instance, so sending another run (if already running) does nothing bad :-)
You can even avoid the direct use of runLoops and switch to a complete GCD approach, using dispatch_source, but I've never used it directly so I can't give you a "good example code" now
NSStream requires a run loop. GCD doesn't provide one. But you don't need GCD here. NSStream is already asynchronous. Just use it on the main thread; that's what it's designed for.
You're also doing several UI interactions while on a background thread. You can't do that. All UI interactions have to occur on the main thread (which is easy if you remove the GCD code).
Where GCD can be useful is if reading and processing the data is time consuming, you can hand that operation to GCD during NSStreamEventHasBytesAvailable.
Use Case
I'm using sockets to send and receive data using CFStreamCreatePairWithSocketToHost() and am trying to wrap my head around how this is done when send multiple sets of data (i.e. not just 1 request).
Problem
Currently I can send data and receive a response (i.e. 1 round trip). However, after I send all the data in the outputStream the stream gets closed (i.e. receives NSStreamEventEndEncountered).
Question
So the question is, what happens when I want to send multiple data requests?
Do I setup a new socket every time I have a new data object to send?
Do I have to reset outputStream and send more data.
Code
Most of this code came from the Cocoa Streams Documentation:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_data = [[NSMutableData alloc] init];
[self initNetworkCommunication];
[self sendString:#"Hello World!"];
}
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"123.456.0.0", 1234, &readStream, &writeStream);
inputStream = (NSInputStream *)readStream; // ivar
[inputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
outputStream = (NSOutputStream *)writeStream; // ivar
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
}
- (void)sendString:(NSString *)string {
NSData *data = [[NSData alloc] initWithData:[string dataUsingEncoding:NSASCIIStringEncoding]];
[_data appendData:data];
[data release];
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
NSLog(#"stream event %u", streamEvent);
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasSpaceAvailable: {
uint8_t *readBytes = (uint8_t *)[_data mutableBytes];
readBytes += byteIndex; // ivar
int data_len = [_data length];
unsigned int len = ((data_len - byteIndex >= 1024) ? 1024 : (data_len - byteIndex));
uint8_t buf[len];
(void)memcpy(buf, readBytes, len);
len = [(NSOutputStream *)theStream write:(const uint8_t *)buf maxLength:len];
NSLog(#"Sending buffer of len: %d", len);
byteIndex += len;
break;
}
case NSStreamEventHasBytesAvailable:
if (theStream == inputStream) {
uint8_t buffer[1024];
int len;
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output) {
NSLog(#"server said: %#", output);
}
}
}
[self sendString:#"Another Test"];
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
NSLog(#"Closing stream...");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[theStream release];
theStream = nil;
break;
default:
NSLog(#"Unknown event");
}
}
Response:
2012-08-15 08:16:30.896 Sockets[34836:f803] Opened input stream.
2012-08-15 08:16:30.898 Sockets[34836:f803] Opened output stream.
2012-08-15 08:16:30.899 Sockets[34836:f803] Sending buffer of len: 12
2012-08-15 08:16:30.900 Sockets[34836:f803] Sending buffer of len: 0
2012-08-15 08:16:30.901 Sockets[34836:f803] Closing output stream.
2012-08-15 08:16:30.939 Sockets[34836:f803] server said: Hello World!
Note the outputStream stream closes after I send the data. I try reinitiating outputStream before [self sendString:#"Another Test"];. I also tried idz's answer.
Per the documentation, I believe the Sending buffer of len: 0 is my problem.
If the delegate receives an NSStreamEventHasSpaceAvailable event and
does not write anything to the stream, it does not receive further
space-available events from the run loop until the NSOutputStream
object receives more bytes. When this happens, the run loop is
restarted for space-available events.
However, the documentation doesn't say anything about closing the stream when the end of the stream is reached. So I'm confused…
A socket is a bidirectional stream connecting two programs, possibly on two different computers. It just transfers the data you write at one end to be read at the other end. It enforces no structure in the data and doesn’t know anything about requests or responses.
The CFStreamCreatePairWithSocketToHost API splits a single connection into two independent streams - one you can read from and one you can write to. This is a nice touch, the underlying socket API uses only one file descriptor both for reading and writing which can get quite confusing.
The connection stays open until one side closes the socket. There is also the possibility of shutting down the socket only in one direction. It is also possible to close the connection only in one direction. If the remote closes it’s read stream your write stream will be closed and vice versa.
Do I setup a new socket every time I have a new data object to send?
You should avoid doing that. It takes some time to establish a new connection, and it takes even more time before your connection gets to full speed. So you should reuse the same connection as much as possible.
Do I have to reset outputStream and send more data.
No, this is not necessary, just send more data.
Per the documentation, I believe the Sending buffer of len: 0 is my
problem.
Writing nothing (that is a buffer of length 0) shouldn’t be a problem. The documentation doesn’t specify what will happen though. So I wrote I test program today to see what will happen, expecting nothing. As it turns out writing a buffer of length 0 closes the output stream. So this was really your problem. I will file a bug on the Apple Bug Reporter about that documentation issue, and so should you.
The part of the documentation you quoted is about something different. If you don’t write after you get a space available notification you won’t get another one until you have written something. That is useful, because so the system doesn’t waste CPU cycles to tell your code over and over again that you could write something if you don’t have anything to write.