GRKInputStreamAggregate 1.0.1

GRKInputStreamAggregate 1.0.1

Maintained by Unclaimed.



GRKInputStreamAggregate

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:@"--%@\r\n", kMultipartFormBoundary]];
		[aggregate addString:[NSString stringWithFormat: @"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n\r\n", kMultipartFormFileInput, fileName]];
		[aggregate addFileURL:self.fileURL];
		[aggregate addString:[NSString stringWithFormat:@"\r\n--%@--\r\n", 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.