Я читаю файл с dispatch_io_read, но при чтении файла мне может потребоваться отменить его.Исходя из документации, я ожидаю, что смогу использовать dispatch_io_read один раз для чтения от 0 до SIZE_MAX (весь файл), а затем вызвать dispatch_io_close (channel, DISPATCH_IO_STOP), чтобы отменить любое новое чтение.Когда я закрываю канал, чтение продолжается, вплоть до конца файла.Я даже пытался разделить чтение файлов на несколько вызовов io_read, думая, что, возможно, это остановится только на одной из этих границ ... Нет.Он просто никогда не остановится.
https://gist.github.com/swillits/f0abb118be1c253f0623ade0cea2b728
@interface Reader : NSObject
+ (instancetype _Nullable)readerForReadingFileURL:(NSURL *)url error:(NSError **)outError;
- (void)beginWithDataAvailableHandler:(void (^)(dispatch_data_t data))dataAvailableHandler completionHandler:(void (^)(NSError * _Nullable))completionHandler queue:(dispatch_queue_t)queue;
- (void)cancel;
@end
@interface Reader ()
@property (readwrite, copy) NSURL * fileURL;
@property (readwrite, retain) dispatch_queue_t dataAvailableQueue;
@property (readwrite, copy, nullable) void (^dataAvailableHandler)(dispatch_data_t data);
@property (readwrite, copy, nullable) void (^completionHandler)(NSError * _Nullable error);
@property (readwrite, copy, nullable) NSError * error;
@property (readwrite, atomic) BOOL cancelled;
@end
#define READ_ENTIRE_FILE 0
@implementation Reader
{
dispatch_io_t _channel;
int _fileDescriptor;
uint64_t _fileSize;
}
+ (instancetype _Nullable)readerForReadingFileURL:(NSURL *)url error:(NSError **)outError;
{
return [[self alloc] initWithFileURL:url error:outError];
}
- (instancetype)initWithFileURL:(NSURL *)url error:(NSError **)outError
{
if (!(self = [super init])) {
return nil;
}
self.fileURL = url;
_fileDescriptor = open(url.relativePath.fileSystemRepresentation, (O_RDONLY), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
if (_fileDescriptor == -1) {
if (outError) {
*outError = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
}
return nil;
}
NSNumber * size = nil;
if (![url getResourceValue:&size forKey:NSURLFileSizeKey error:outError]) {
return nil;
}
_fileSize = size.unsignedLongLongValue;
return self;
}
- (void)beginWithDataAvailableHandler:(void (^)(dispatch_data_t data))dataAvailableHandler completionHandler:(void (^)(NSError * _Nullable))completionHandler queue:(dispatch_queue_t)queue
{
self.completionHandler = completionHandler;
self.dataAvailableHandler = dataAvailableHandler;
_channel = dispatch_io_create(DISPATCH_IO_STREAM, _fileDescriptor, queue, ^(int error) {
NSError * nserr = nil;
if (self.error) {
nserr = self.error;
} else if (error) {
nserr = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:(NSInteger)error userInfo:nil];
}
close(self->_fileDescriptor);
self->_fileDescriptor = 0;
self.completionHandler(nserr);
self.completionHandler = nil;
self.dataAvailableHandler = nil;
});
assert(_channel);
dispatch_io_set_low_water(_channel, 1024 * 1024 * 1);
dispatch_io_set_high_water(_channel, 1024 * 1024 * 4);
// Maybe splitting the file reading into multiple dispatch_io_read calls will let cancelling stop on one of these boundaries? Nope. It still does not stop until the entire file is read.
#if READ_ENTIRE_FILE
size_t chunkSize = SIZE_MAX;
#else
size_t chunkSize = 1024 * 1024 * 1;
#endif
for (off_t currentOffset = 0; currentOffset < _fileSize; currentOffset += chunkSize) {
BOOL isLastChunk = (currentOffset + chunkSize >= _fileSize);
dispatch_io_read(_channel, currentOffset, chunkSize, queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (data) {
if (self.error) {
NSLog(@"%d", (int)currentOffset);
}
NSLog(@"Reader read %zu bytes", dispatch_data_get_size(data));
self.dataAvailableHandler(data);
}
if (error) {
NSLog(@"%d", error);
if (error == ECANCELED) {
self.error = [[NSError alloc] initWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil];
} else if (error) {
self.error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:(NSInteger)error userInfo:nil];
}
}
// Maybe cancelling in the data handler is necessary? Still doesn't cancel anything
if (self.cancelled || isLastChunk) {
dispatch_io_close(self->_channel, DISPATCH_IO_STOP);
dispatch_io_close(self->_channel, 0); // let's try everything!
}
});
}
}
- (void)cancel
{
self.cancelled = YES;
// I'm expecting *this* to cancel everything... It does absolutely nothing in every case I tested.
dispatch_io_close(self->_channel, DISPATCH_IO_STOP);
}
@end
Документация , этот SO ответ , илюбой другой фрагмент кода, который я смог найти, подсказывает мне, что хотя бы один из этих подходов должен работать.
Я действительно очень смущен этим.