Latest 1.0
Homepage https://github.com/levigroker/GRKInputStreamAggregate
License Apache License, Version 2.0
Platforms ios 7.0, osx 10.9
Frameworks Foundation
Authors

Build Status
Version
[Platform]()
License

A stream aggregator that reads from a concatenated sequence of other inputs. Use this to
combine multiple input streams (and data blobs) together into one. This is useful when
uploading multipart MIME bodies.

Installing

If you’re using CocoPods it’s as simple as adding this to your
Podfile:

pod 'GRKInputStreamAggregate'

otherwise, simply add the contents of the GRKInputStreamAggregate subdirectory to your
project.

Documentation

As an example, the aggregate can be used to provide a concatenated stream for a multipart
file upload.

To do this, one would create a NSURLSessionUploadTask via NSURLSessions uploadTaskWithStreamedRequest: method.
Example:

    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
    [urlRequest setHTTPMethod:@"POST"];
    [urlRequest setValue: [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"];

    //NOTE: We must pass `[NSOperationQueue mainQueue]` as the `delegateQueue`
    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    NSURLSessionUploadTask *uploadTask = [urlSession uploadTaskWithStreamedRequest:urlRequest];

Then, implement the task delegate method URLSession:task:needNewBodyStream:.
Example:

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
    {
        NSString *fileName = [self.fileURL lastPathComponent];

        //Ensure any previous aggregate gets closed
        [self.aggregate close];

        //Create a new aggregate for the body stream
        GRKInputStreamAggregate *aggregate = [[GRKInputStreamAggregate alloc] init];

        //Build our body stream by aggregating the multipart boundaries with our file.
        [aggregate addString:[NSString stringWithFormat:@"--%@rn", kMultipartFormBoundary]];
        [aggregate addString:[NSString stringWithFormat: @"Content-Disposition: form-data; name="%@"; filename="%@"rnrn", kMultipartFormFileInput, fileName]];
        [aggregate addFileURL:self.fileURL];
        [aggregate addString:[NSString stringWithFormat:@"rn--%@--rn", kMultipartFormBoundary]];

        self.aggregate = aggregate;

        NSInputStream *inputStream = [aggregate openForInputStream];

        completionHandler(inputStream);
    }

…and be sure to close the aggregate when done, in the URLSession:task:didCompleteWithError:
delegate method.
Example:

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
        //Ensure any previous aggregate gets closed
        [self.aggregate close];
    }

As an important side note, when creating your NSURLSession you must pass [NSOperationQueue mainQueue] as the delegateQueue.
This is required because GRKInputStreamAggregate uses NSStreams under the covers and
those must be associated with an active run loop. It may be possible to utilize the
CFWriteStreamSetDispatchQueue method from CFStream.h to replace the need for the run
loop integration, however I have, as yet, been unsuccessful in getting this to work
without introducing race conditions and deadlocks. Pull requests are welcome… ;)

Disclaimer and Licence

  • This derivative work is based on work from the Couchbase Lite iOS project.
    Specifically the CBLMultiStreamWriter class, created by Jens Alfke of Couchbase.
    Neither Couchbase nor Jens Alfke endorse me (Levi Brown) or this derivative work.
    Please see the license file ./GRKInputStreamAggregate/LICENSE.txt
  • This work is licensed under the Apache License, Version 2.0.
    Please see the included LICENSE.txt for complete details.

About

A professional iOS engineer by day, my name is Levi Brown. Authoring a blog
grokin.gs, I am reachable via:

Twitter @levigroker
Email [email protected]

Your constructive comments and feedback are always welcome.

Latest podspec

{
    "name": "GRKInputStreamAggregate",
    "version": "1.0",
    "summary": "A stream aggregator that reads from a concatenated sequence of other inputs.",
    "description": "A stream aggregator that reads from a concatenated sequence of other inputs. Use this to combine multiple input streams (and data blobs) together into one. This is useful when uploading multipart MIME bodies.",
    "homepage": "https://github.com/levigroker/GRKInputStreamAggregate",
    "license": "Apache License, Version 2.0",
    "authors": {
        "Levi Brown": "[email protected]"
    },
    "social_media_url": "https://twitter.com/levigroker",
    "source": {
        "git": "https://github.com/levigroker/GRKInputStreamAggregate.git",
        "tag": "1.0"
    },
    "platforms": {
        "ios": "7.0",
        "osx": "10.9"
    },
    "source_files": "GRKInputStreamAggregate/**/*.{h,m}",
    "frameworks": "Foundation"
}

Pin It on Pinterest

Share This