Latest 2.3.5
Homepage https://github.com/pavelosipov/POSInputStreamLibrary
License MIT
Platforms ios 7.0, requires ARC
Frameworks Foundation, AssetsLibrary, UIKit, ImageIO, Photos*
Authors

NSInputStream for ALAsset

Version

POSInputStreamLibrary contains NSInputStream implementation which uses ALAsset
as its data source. The main features of POSBlobInputStream are the following:

  • Synchronous and asynchronous working modes.
  • Autorefresh after ALAsset invalidation.
  • Smart caching of ALAsset while reading its data.
  • Using NSStreamFileCurrentOffsetKey property for read offset specification.
  • Autorecovery after ALAssetRepresentation invalidation.
  • Adjustment filters detection and applying for both iOS 7 and iOS 8 (new in 2.0.0).
  • Integration with CFNetwork framework.
  • Integration with AFNetworking (thanks to @bancek).

The category for NSInputStream defines initializers for the most common cases:

@interface NSInputStream (POS)
+ (NSInputStream *)pos_inputStreamWithAssetURL:(NSURL *)assetURL;
+ (NSInputStream *)pos_inputStreamWithAssetURL:(NSURL *)assetURL asynchronous:(BOOL)asynchronous;
+ (NSInputStream *)pos_inputStreamForCFNetworkWithAssetURL:(NSURL *)assetURL;
+ (NSInputStream *)pos_inputStreamForAFNetworkingWithAssetURL:(NSURL *)assetURL;
@end

All of them require NSURL of ALAsset. POSBlobInputStream will query
ALAssetLibrary for ALAsset during the opening.

Working modes

Synchronous

In sync mode all methods of POSInputStream completely perform their work during
the call. If it is necessary to obtain some data from ALAssetLibrary the
calling thread will be blocked. This makes possible to work with a stream without
subscribing to its events, but at the same time neither method of NSInputStream
should be called from the main thread. The reason is that ALAssetLibrary
interacts with the client code in the main thread. Thus there will be a deadlock if
POSBlobInputStream waits the answer from ALAssetLibrary in a blocked main
thread. Here is an example of POSBlobInputStream usage in a sync mode for
calculating checksum of ALAsset.

NSInputStream *stream = [NSInputStream pos_inputStreamWithAssetURL:assetURL asynchronous:NO];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [stream open];
    if ([stream streamStatus] == NSStreamStatusError) {
        // Error notification
        [stream close];
        return;
    }
    NSParameterAssert([stream streamStatus] == NSStreamStatusOpen);
    while ([stream hasBytesAvailable]) {
        uint8_t buffer[kBufferSize];
        const NSInteger readCount = [stream read:buffer maxLength:kBufferSize];
        if (readCount < 0) {
            break;
        } else {
            // Checksum update
        }
    }
    if ([stream streamStatus] != NSStreamStatusAtEnd) {
        // Error notification
    }
    [stream close];
}

Asynchronous

In async mode all methods of POSBlobInputStream return immediately after call.
Client code should provide a delegate to the stream to receive information about its
status. This is the only way to know when the stream opened, when it has data to read
and about errors. You can see async version of checksum calculation below.

@interface ChecksumCalculator () <NSStreamDelegate>
@end

@implementation ChecksumCalculator

- (void)calculateChecksumForStream:(NSInputStream *)aStream {
    aStream.delegate = self;
    [aStream open];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [aStream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
        for (;;) { @autoreleasepool {
            if (![runLoop runMode:NSDefaultRunLoopMode
                       beforeDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]]) {
                break;
            }
            const NSStreamStatus streamStatus = [aStream streamStatus];
            if (streamStatus == NSStreamStatusError || streamStatus == NSStreamStatusClosed) {
                    break;
            }
        }}
    }});
}

#pragma mark - NSStreamDelegate

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    switch (eventCode) {
        case NSStreamEventHasBytesAvailable: {
            [self updateChecksumForStream:aStream];
        } break;
        case NSStreamEventEndEncountered: {
            [self notifyChecksumCalculationCompleted];
            [aStream close];
        } break;
        case NSStreamEventErrorOccurred: {
            [self notifyErrorOccurred:[aStream streamError]];
            [aStream close];
        } break;
    }
}

@end

Integrating with NSURLRequest

POSBlobInputStream provides pos_inputStreamForCFNetworkWithAssetURL initializer
for NSURLRequest integration. It takes into account the following CFNetwork "features":

  • CFNetwork works with a stream in a sync mode.
  • CFNetowrk uses deprecated CFReadStreamGetError method to get error description from
    the stream. This action will crash the app because of the bug in a "toll-free bridging"
    implementation for NSInputStream. This is the reason why streamStatus method will never
    return NSStreamStatusError. More over, POSBlobInputStream will not notify about its
    status change via C-callbacks. The only way to receive actual status of the stream is via
    NSStreamDelagate callback.

Integrating with AFNetworking

POSBlobInputStream provides pos_inputStreamForAFNetworkingWithAssetURL initializer
for AFNetworking integration.

Example:

- (void) uploadAsset:(ALAsset*)asset toUrl:(NSString*)url
             success:(void (^)(id responseObject))success
             failure:(void (^)(NSError* error))failure {
    ALAssetRepresentation *assetRepresentation = asset.defaultRepresentation;
    NSString* assetFilename = assetRepresentation.filename;
    NSURL *assetUrl = assetRepresentation.url;
    unsigned long long assetSize = assetRepresentation.size;
    NSInputStream *assetInputStream = [NSInputStream pos_inputStreamForAFNetworkingWithAssetURL:assetUrl];
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    AFHTTPRequestOperation *op = [manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithInputStream:assetInputStream name:@"file" fileName:assetFilename length:assetSize mimeType:@"image/jpeg"];
    } success:^(AFHTTPRequestOperation *operation, id responseObject) {
        success(responseObject);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        failure(error);
    }];
}

See full example for more details.

Resources

Latest podspec

{
    "name": "POSInputStreamLibrary",
    "version": "2.3.5",
    "license": "MIT",
    "summary": "NSInputStream implementation for ALAsset and other kinds of data source.",
    "homepage": "https://github.com/pavelosipov/POSInputStreamLibrary",
    "authors": {
        "Pavel Osipov": "[email protected]"
    },
    "source": {
        "git": "https://github.com/pavelosipov/POSInputStreamLibrary.git",
        "tag": "2.3.5"
    },
    "platforms": {
        "ios": "7.0"
    },
    "requires_arc": true,
    "source_files": "POSInputStreamLibrary/*.{h,m}",
    "frameworks": [
        "Foundation",
        "AssetsLibrary",
        "UIKit",
        "ImageIO"
    ],
    "weak_frameworks": "Photos"
}

Pin It on Pinterest

Share This