dispatch_io_close不会取消文件读取

时间:2019-02-28 21:14:19

标签: cocoa io grand-central-dispatch dispatch

我正在使用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以及其他所有代码向我暗示,这些方法中的至少一种应该有效。

我对此感到非常困惑。

0 个答案:

没有答案