Hi based on this answer I wrote subclass of NSInputStream
and it works pretty well.
Now It turned out that I have scenario where I'm feeding to server large amount of data and to prevent starvation of other services I need control speed of feeding data. So I improved functinality of my subclass with following conditions:
- when data should be postponed,
hasBytesAvailable
returnsNO
and reading attempts ends with zero bytes read - when data can be send,
- read:maxLength:
allows to read some maximum amount data at once (by default 2048). - when
- read:maxLength:
returns zero bytes read, needed delay is calculated and after that delayNSStreamEventHasBytesAvailable
event is posted.
Here is interesting parts of code (it is mixed with C++):
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
if (![self isOpen]) {
return kOperationFailedReturnCode;
}
int delay = 0;
NSInteger readCount = (NSInteger)self.cppInputStream->Read(buffer, len, delay);
if (readCount<0) {
return kOperationFailedReturnCode;
}
LOGD("Stream") << __PRETTY_FUNCTION__
<< " len: " << len
<< " readCount: "<< readCount
<< " time: " << (int)(-[openDate timeIntervalSinceNow]*1000)
<< " delay: " << delay;
if (!self.cppInputStream->IsEOF()) {
if (delay==0)
{
[self enqueueEvent: NSStreamEventHasBytesAvailable];
} else {
NSTimer *timer = [NSTimer timerWithTimeInterval: delay*0.001
target: self
selector: @selector(notifyBytesAvailable:)
userInfo: nil
repeats: NO];
[self enumerateRunLoopsUsingBlock:^(CFRunLoopRef runLoop) {
CFRunLoopAddTimer(runLoop, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
}];
}
} else {
[self setStatus: NSStreamStatusAtEnd];
[self enqueueEvent: NSStreamEventEndEncountered];
}
return readCount;
}
- (void)notifyBytesAvailable: (NSTimer *)timer {
LOGD("Stream") << __PRETTY_FUNCTION__ << "notifyBytesAvailable time: " << (int)(-[openDate timeIntervalSinceNow]*1000);
[self enqueueEvent: NSStreamEventHasBytesAvailable];
}
- (BOOL)hasBytesAvailable {
bool result = self.cppInputStream->HasBytesAvaible();
LOGD("Stream") << __PRETTY_FUNCTION__ << ": " << result << " time: " << (int)(-[openDate timeIntervalSinceNow]*1000);
return result;
}
I wrote some test for that and it worked.
Problem appeared when I used this stream with NSURLSession
as source of body of HTTP request. From logs I can see that NSURLSession
tries to read everything at once. On first read I return limited portion of data. Immediately after that NSURLSession
asks if there are bytes available (I return NO
).
After some time (for example 170 ms), I'm sending notification that bytes are now available but NSURLSession
doesn't respond to that and do not invoke any method of my stream class.
Here is what I see in logs (when running some test):
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper open]
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 1 time: 0
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper read:maxLength:] len: 32768 readCount: 2048 time: 0 delay: 170
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0
09:32:15161[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper notifyBytesAvailable:]notifyBytesAvailable time: 171
Where time is amount of milliseconds since stream has been opened.
Looks looks NSURLSession
is unable to handle input streams with limited data rate.
Does anyone else had similar problem?
Or has alternative concept how to achieve bandwidth management on NSURLSession
?