It’s been a long haul. Here’s some followup on this issue:
Early on, I threw out the idea of maintaining and checking a leftover cache because that would have worked only for the output stream, when further reflection suggested that the input stream could also become blocked.
Instead, I set up idling while-loops:
- (void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode)
// RECEIVING
case NSStreamEventHasBytesAvailable: {
if (self.receiveStage == kNothingToReceive)
return;
// Get the data from the stream. (This method returns NO if bytesRead < 1.)
if (![self receiveDataViaStream:(NSInputStream *)theStream]) {
// If nothing was actually read, consider the stream to be idling.
self.bStreamIn_isIdling = YES;
// Repeatedly retry read, until (1) the read is successful, or (2) stopNetwork is called, which will clear the idler.
// (Just in case, add nil stream property as a loop breaker.)
while (self.bStreamIn_isIdling && self.streamIn) {
if ([self receiveDataViaStream:(NSInputStream *)theStream]) {
self.bStreamIn_isIdling = NO;
// The stream will have started up again; prepare for next event call.
[self assessTransmissionStage_uponReadSuccess];
}
}
}
else
// Prepare for what happens next.
[self assessTransmissionStage_uponReadSuccess];
break;
// SENDING
case NSStreamEventHasSpaceAvailable:
if (self.sendStage == kNothingToSend)
return;
if (![self sendDataViaStream:(NSOutputStream *)theStream]) {
self.bStreamOut_isIdling = YES;
while (self.bStreamOut_isIdling && self.streamOut) {
if ([self sendDataViaStream:(NSOutputStream *)theStream]) {
self.bStreamOut_isIdling = NO;
[self assessTransmissionStage_uponWriteSuccess];
}
}
}
else
[self assessTransmissionStage_uponWriteSuccess];
break;
// other event cases…
Then it came time to test a user-initiated cancellation, via a “cancel” button. Midway through the sync, there’s a pause on the Cocoa side, awaiting user input. If the user cancels at this point, the Cocoa app closes the streams and removes them from the runloop, so I expected that the streams on the other side of the connection would generate NSStreamEventEndEncountered
events, or perhaps NSStreamEventErrorOccurred
. But, no, only one event came through, an NSStreamEventHasBytesAvailable
! Go figure.
Of course, there weren’t really any “bytes available,” as the stream had been closed on the Cocoa side, not written to — so the stream handler on the iOS side went into an infinite loop. Not so good.
Next I tested what would happen if one of the devices went to sleep. During the pause for user input, I let the iPhone sleep via auto-lock*, and then supplied the user input on the Cocoa side. Surprise again: the Cocoa app continued without perturbation to the end of the sync, and when I woke up the iPhone, the iOS app proved to have completed its side of the sync too.
Could there have been a hiccup on the iPhone side that was fixed by my idle loop? I threw in a stop-network routine to check:
if (![self receiveDataViaStream:(NSInputStream *)theStream])
[self stopNetwork]; // closes the streams, etc.
The sync still ran through to completion. There was no hiccup.
Finally, I tested what happened if the Mac (the Cocoa side) went to sleep during that pause for input. This produced a sort of backward belch: Two NSStreamEventErrorOccurred
events were received — on the Mac side, after which it was no longer possible to write to the output stream. No events at all were received on the iPhone side, but if I tested the iPhone's stream status, it would return 5, NSStreamStatusAtEnd.
CONCLUSIONS & PLAN:
- The "temporary block" is something of a unicorn. Either the network runs smoothly or it disconnects altogether.
- If there is truly such a thing as a temporary block, there is no way to distinguish it from a complete disconnection. The only stream-status constants that seem logical for a temporary block are are
NSStreamStatusAtEnd
and NSStreamStatusError
. But per the above experiments, these indicate disconnection.
- As a result of which I’m discarding the while-loops and am detecting disconnection solely by checking for bytesRead/Written < 1.
*The iPhone won’t ever sleep if it’s slaved to Xcode. You have to run it straight from the iPhone.