2

I am trying to implement an Upload with random Data and measure the speed. For now i am generating my random NSData like this:

void * bytes = malloc("");
NSData * myData = [NSData dataWithBytes:bytes length:"bytes"];
free("bytes");

But there will be Memory Problems if i want to upload a big File...

My Upload process is like this:

NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];

NSURLSession *session =
[NSURLSession sessionWithConfiguration:sessionConfig
                              delegate:self
                         delegateQueue:nil];

NSURL * urll = [NSURL URLWithString:UPLOAD_SERVER];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:urll];
[urlRequest setHTTPMethod:@"POST"];
[urlRequest addValue:@"Keep-Alive" forHTTPHeaderField:@"Connection"];

NSString *boundary = @"*****";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[urlRequest addValue:contentType forHTTPHeaderField: @"Content-Type"];

NSMutableData *body = [NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// setting the body of the post to the reqeust
[urlRequest setHTTPBody:body];

void * bytes = malloc(250000000);
NSData * uploadData = [NSData dataWithBytes:bytes length:250000000];
free(bytes);

ulTask = [session uploadTaskWithRequest:urlRequest fromData:uploadData];

[ulTask resume];

Is there a way to upload with a buffer or something?! like generate small data, upload this and generate a new one and upload again?!

davidOhara
  • 1,008
  • 5
  • 17
  • 39
  • @Rob Thats exactly what i mean...Do you have an example for the NSInputStream class?! – davidOhara Apr 04 '14 at 16:03
  • 1
    See BJ Homer's article about [Subclassing NSInputStream](http://bjhomer.blogspot.com/2011/04/subclassing-nsinputstream.html) in conjunction with `NSURLSession` or `NSURLConnection`. I've incorporated the relevant bits into my answer, below. – Rob Apr 04 '14 at 17:57
  • Without a doubt if one is going to subclass `NSInputStream` start with BJ Homer's article. That said make sure you need to, it is not trivial. – zaph Apr 04 '14 at 19:57

2 Answers2

3

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • How come `ViewController`’s declaration does not need to define a primary class? – siavashk Oct 12 '17 at 20:16
  • 1
    In the `.h` file, I _would_ define `ViewController` as a `UIViewController` (or whatever) subclass. But in the `.m` file, I have a private class extension (`@interface ViewController ()`) to define private properties that don't belong in the public `.h` file. I just didn't bother include the `.h` file here, though, because it's not really relevant to the question at hand. – Rob Oct 12 '17 at 20:22
  • Thanks for the answer and the helpful comment. If I want to use the `ViewController` to control uploading in the backend, what should I subclass it from? Assume that I am calling `someProgressCallback` function in `didSendBodyData` and `someResultCallback` function in `didCompleteWithError`. – siavashk Oct 12 '17 at 20:58
  • What you subclass the view controller (VC) from is a just question of your UI. Usually it would be a `UIViewController`, but it could be a `UITableViewController` or `UICollectionViewController`, too. Whatever your UI requires. But your real question is where you should put these delegate methods. Frankly, I would generally avoid putting them in some massive view controller, and instead create a dedicated network manager, a `NSObject` subclass and put the delegate methods there. And then the VC could supply the `someProgressCallback` and `someResultCallback` blocks to this network manager. – Rob Oct 12 '17 at 21:37
1
-(void) updateUserData:(NSDictionary*)data
     withImageToUpload:(NSData*)imageToUpload
               success: (void (^) (id responseObject))success
               failure: (void (^)(NSError* error))failure
{
    NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"PATCH"
                                                                                              URLString:@"URL_REQUEST/update_profile"
                                                                                             parameters:data
                                                                              constructingBodyWithBlock:^(id<AFMultipartFormData> formData)
                                    {
                                        [formData appendPartWithFileData:imageToUpload
                                                                    name:@"individual[avatar]"
                                                                fileName:@"avatar.jpg"
                                                                mimeType:@"image/jpeg"];
                                    }

                                                                                                  error:nil];

    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSProgress *progress = nil;

    NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request
                                                                       progress:&progress
                                                              completionHandler:^(NSURLResponse *response, id responseObject, NSError *error)
                                          {
                                              if (error)
                                              {
                                                  NSLog(@"Error: %@", error);
                                                  failure(error);
                                              } else
                                              {
                                                  [[NSNotificationCenter defaultCenter] postNotificationName:@"userDataDidUpdated" object:self];
                                                  success(responseObject);
                                              }
                                          }];

    [uploadTask resume];

}
Atef
  • 2,872
  • 1
  • 36
  • 32