ALAsset
ALAsset
objectALAsset
NSURLRequest
for streaming into the network. @interface NSInputStream (NSInputStreamExtensions) // ... + (id)inputStreamWithFileAtPath:(NSString *)path; // ... @end
@interface NSMutableURLRequest (NSMutableHTTPURLRequest) // ... - (void)setHTTPBodyStream:(NSInputStream *)inputStream; // ... @end
POSBlobInputStream
class was developed. It is initialized by the URL of the gallery object and reads the data directly without creating temporary files. @interface NSInputStream (POS) + (NSInputStream *)pos_inputStreamWithAssetURL:(NSURL *)assetURL; + (NSInputStream *)pos_inputStreamWithAssetURL:(NSURL *)assetURL asynchronous:(BOOL)asynchronous; + (NSInputStream *)pos_inputStreamForCFNetworkWithAssetURL:(NSURL *)assetURL; @end
@interface NSInputStream : NSStream - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len; - (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len; - (BOOL)hasBytesAvailable; @end
getBuffer:length:
is not necessary to support, so it would seem that only 2 methods need to be implemented. Their mapping to the ALAssetRepresentation
interface also did not cause ALAssetRepresentation
issues. @interface ALAssetRepresentation : NSObject // ... - (long long)size; - (NSUInteger)getBytes:(uint8_t *)buffer fromOffset:(long long)offset length:(NSUInteger)length error:(NSError **)error; // ... @end
POSBlobInputStream
into the water, I was unpleasantly surprised. The call of any method of the NSStream base class ended with the exception of the form: *** -propertyForKey: only defined for abstract class. Define -[POSBlobInputStream propertyForKey:]
NSInputStream
is an abstract class, and each of its init methods creates an object from one of the NSInputStream
classes. In Objective-C, this pattern is called class cluster . Thus, the implementation of its own stream requires the implementation, including all NSStream
methods, and there is a room full of them. @interface NSStream : NSObject - (void)open; - (void)close; - (id <NSStreamDelegate>)delegate; - (void)setDelegate:(id <NSStreamDelegate>)delegate; - (id)propertyForKey:(NSString *)key; - (BOOL)setProperty:(id)property forKey:(NSString *)key; - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - (NSStreamStatus)streamStatus; - (NSError *)streamError; @end
POSBlobInputStream
most difficult was to implement a mechanism for asynchronous notification of state changes. In NSStream
the scheduleInRunLoop:forMode:
removeFromRunLoop:forMode:
and setDelegate:
methods are responsible for it. Thanks to them, you can create threads that at the time of opening do not have a byte of information. POSBlobInputStream
exploits this feature for the following purposes:open
method. POSBlobInputStream
is considered open as soon as it was able to obtain an ALAssetRepresentation
object by its NSURL
. As you know, using the iOS SDK, this can only be done asynchronously. Thus, the presence of a mechanism for asynchronous notification of a stream status change from NSStreamStatusNotOpen
to NSStreamStatusOpen
or NSStreamStatusError
is very useful here.NSStreamEventHasBytesAvailable
event. NSInputStream *stream = [NSInputStream pos_inputStreamWithAssetURL:assetURL asynchronous:NO]; [stream open]; if ([stream streamStatus] == NSStreamStatusError) { /* */ return; } NSParameterAssert([stream streamStatus] == NSStreamStatusOpen); while ([stream hasBytesAvailable]) { uint8_t buffer[kBufferSize]; const NSInteger readCount = [stream read:buffer maxLength:kBufferSize]; if (readCount < 0) { /* */ return; } else if (readCount > 0) { /* */ } } if ([stream streamStatus] != NSStreamStatusAtEnd) { /* */ return; } [stream close];
ALAsset
in the main thread. If the open
function itself is called in the main thread, then we get a classic deadlock. Why a synchronous implementation of the flow was needed at all will be described below in the section “Features of Integration with NSURLRequest”. @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), ^{ 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]; [_stream close]; } break; case NSStreamEventErrorOccurred: { [self notifyErrorOccurred:[_stream streamError]]; [_stream close]; } break; } } @end
ChecksumCalculator
sets itself up as a POSBlobInputStream
event POSBlobInputStream
. As soon as a stream has new data, or, on the contrary, ends, or an error occurs, it sends the corresponding events. Please note that it is possible to specify in which thread to send them. For example, in the code listing below, they will come into a workflow created by GCD.ALAssetRepresentation
methods is very expensive. POSBlobInputStream
tries to minimize their number by caching the results. For example, there is a minimum data block that will be read when the read:maxLength:
method is called, and only after it has been exhausted will a new call occur.ALAssetRepresentation
may become invalid. So, on iOS 5.x this happens when saving a photo to the phone’s gallery. From the point of view of client code, this looks like the return of a null value by the getBytes:fromOffset:length:error:
method of the getBytes:fromOffset:length:error:
object. In this case, it is known that the data are not fully read. In this case, POSBlobInputStream
receives the ALAssetRepresentation
again. It is worth noting that when working in synchronous mode at the time of reinitialization, the calling thread is blocked, but not in the asynchronous mode.NSURLRequest
in particular is based on the CFNetwork framework. Over the long years of his life, he has amassed quite a few cabinets with skeletons. But first things first.NSInputStream
is one of the " toll-free bridged " classes of the iOS SDK. It can be brought to CFReadStreamRef and work with it in the future as with an object of this type. This property underlies the implementation of NSURLRequest
. The latter issues POSBlobInputStream
for its twin brother, and CFNetwork communicates with it already using the C-interface. In theory, all C-calls to CFReadStream
should be CFReadStream
to calls to the corresponding NSInputStream
methods. However, in practice there are two serious deviations:NSInputStream
informs CFNetwork about an error, the framework tries to get its description using this unfortunate function. The result is sad.POSBlobInputStream
got the property shouldNotifyCoreFoundationAboutStatusChange
. If the flag is set, then:streamStatus
method streamStatus
never return NSStreamStatusError
NSStreamDelegate
protocol by some class and set it as a delegate to the stream (see the example of checksum calculation above).open
method is called several times in a loop, and if the stream does not have time to go to the open state during this time interval, it is recognized as spoiled. This feature of the network framework was the reason for the POSBlobInputStream
support of synchronous operation, albeit with limitations.POSBlobInputStream
was trained to read the contents of a photo not from the beginning, but from a certain position. The offset in it is set by the NSStreamFileCurrentOffsetKey
property. Due to the fact that it is also used to shift the beginning of the standard file stream, it is possible to specify it uniformly.POSBlobInputStream
was created to upload photos and videos from the gallery. However, it is designed so that, if necessary, you can use other data sources. For streaming from other sources, it is necessary to implement the POSBlobInputStreamDataSource
protocol. @protocol POSBlobInputStreamDataSource <NSObject> // // Self-explanatory KVO-compliant properties. @property (nonatomic, readonly, getter = isOpenCompleted) BOOL openCompleted; @property (nonatomic, readonly) BOOL hasBytesAvailable; @property (nonatomic, readonly, getter = isAtEnd) BOOL atEnd; @property (nonatomic, readonly) NSError *error; // // This selector will be called before anything else. - (void)open; // // Data Source configuring. - (id)propertyForKey:(NSString *)key; - (BOOL)setProperty:(id)property forKey:(NSString *)key; // // Data Source data. // The contracts of these selectors are the same as for NSInputStream. - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)maxLength; - (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)bufferLength; @end
#pragma mark Undocumented but necessary NSStream Overrides (fuck you Apple) - (void) _scheduleInCFRunLoop:(NSRunLoop*) inRunLoop forMode:(id)inMode { /* FUCK YOU APPLE */ } - (void) _setCFClientFlags:(CFOptionFlags)inFlags callback:(CFReadStreamClientCallBack)inCallback context:(CFStreamClientContext)inContext { /* NO SERIOUSLY, FUCK YOU */ }
POSBlobInputStream
, in turn, is one of the key components of the Mail.Ru Cloud application. During the service, he was tested in battle by an army of users. Many rakes were collected and leveled, and at the moment the flow is one of the most stable components. Use, write extensions, and, of course, I will be glad to any feedback.Source: https://habr.com/ru/post/216247/
All Articles