I would suggest just start an upload and just keep sending data. You can also avoid the creation of your 250mb buffer, by using uploadTaskWithStreamedRequest
and then create an NSInputStream
subclass that just keeps providing more data until you tell it to stop. You can implement URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
to monitor the progress of your upload (so you can presumably monitor the speed with which data is being sent).
Anyway, to create the upload request:
@interface ViewController () <NSURLSessionDelegate, NSURLSessionTaskDelegate>
@property (nonatomic, strong) CustomStream *inputStream;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.inputStream = [[CustomStream alloc] init];
NSURL *url = [NSURL URLWithString:kURLString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionUploadTask *task = [session uploadTaskWithStreamedRequest:request];
[task resume];
// I don't know how you want to finish the upload, but I'm just going
// to stop it after 10 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.inputStream.finished = YES;
});
}
You obviously have to implement the appropriate delegate methods:
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
NSLog(@"%lld %lld %lld", bytesSent, totalBytesSent, totalBytesExpectedToSend);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{
completionHandler(self.inputStream);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(@"%s: error = %@; data = %@", __PRETTY_FUNCTION__, error, [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]);
}
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
self.responseData = [NSMutableData data];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
[self.responseData appendData:data];
}
And the CustomStream
:
static NSInteger const kBufferSize = 32768;
@interface CustomStream : NSInputStream
@property (nonatomic, readonly) NSStreamStatus streamStatus;
@property (nonatomic, getter = isFinished) BOOL finished;
@end
@interface CustomStream ()
@property (nonatomic) NSStreamStatus streamStatus;
@property (nonatomic) void *buffer;
@end
@implementation CustomStream
- (instancetype)init
{
self = [super init];
if (self) {
_buffer = malloc(kBufferSize);
NSAssert(_buffer, @"Unable to create buffer");
memset(_buffer, 0, kBufferSize);
}
return self;
}
- (void)dealloc
{
if (_buffer) {
free(_buffer);
self.buffer = NULL;
}
}
- (void)open
{
self.streamStatus = NSStreamStatusOpen;
}
- (void)close
{
self.streamStatus = NSStreamStatusClosed;
}
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
{
if ([self isFinished]) {
if (self.streamStatus == NSStreamStatusOpen) {
self.streamStatus = NSStreamStatusAtEnd;
}
return 0;
}
NSUInteger bytesToCopy = MIN(len, kBufferSize);
memcpy(buffer, _buffer, bytesToCopy);
return bytesToCopy;
}
- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len
{
return NO;
}
- (BOOL)hasBytesAvailable
{
return self.streamStatus == NSStreamStatusOpen;
}
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
#pragma mark Undocumented CFReadStream Bridged Methods
- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags
callback:(__unused CFReadStreamClientCallBack)inCallback
context:(__unused CFStreamClientContext *)inContext {
return NO;
}
@end
I'd suggest you refer to BJ Homer's article Subclassing NSInputStream for some of the background on a few of the cryptic methods in this NSInputStream
subclass.