我正在使用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
我可以找到的documentation,此SO answer以及其他所有代码向我暗示,这些方法中的至少一种应该有效。
我对此感到非常困惑。