
During the service for the benefit of Mail.Ru Cloud, the POSBlobInputStream stream has acquired a very rich functionality:ALAssetALAsset objectALAsset
Until all the functionality of the application was limited to downloading photos, everything was simple. The image from the gallery was saved to a temporary file, on the basis of which the standard file stream was created. The latter was fed to the input of 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 NSStreamStatusErrorNSStreamDelegate 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