NSOperation 中的 NSURLSessionDownloadTask 在取消时崩溃

NSURLSessionDownloadTask in NSOperation crashes on cancel

我正在尝试创建 NSOperation 的 DownloadOperation 子类来异步下载数据。在我尝试添加取消支持之前,一切似乎都运行良好。基本上,操作的 NSURLSessionDownloadTask 的完成处理程序似乎在操作释放后被调用。它会在 weakSelf.state = kFinished.

行以 EXC_BAD_ACCESS 崩溃

完整的示例项目在这里:https://github.com/angstsmurf/DownloadOperationQueue。按 Command+。 运行 崩溃后。

#import "DownloadOperation.h"

typedef enum OperationState : NSUInteger {
    kReady,
    kExecuting,
    kFinished
} OperationState;

@interface DownloadOperation ()

@property NSURLSessionDownloadTask *task;

@property OperationState state;

@end


@implementation DownloadOperation


// default state is ready (when the operation is created)

@synthesize state = _state;

- (void)setState:(OperationState)state {
    @synchronized(self) {
        if (_state != state) {
            [self willChangeValueForKey:@"isExecuting"];
            [self willChangeValueForKey:@"isFinished"];
            _state = state;
            [self didChangeValueForKey: @"isExecuting"];
            [self didChangeValueForKey: @"isFinished"];
        }
    }
}

- (OperationState)state {
    @synchronized (self) {
        return _state;
    }
}

- (BOOL)isReady { return (self.state == kReady); }
- (BOOL)isExecuting { return (self.state  == kExecuting); }
- (BOOL)isFinished { return (self.state  == kFinished); }

- (BOOL)isAsynchronous {
    return YES;
}

- (instancetype)initWithSession:(NSURLSession *)session downloadTaskURL:(NSURL *)downloadTaskURL completionHandler:(nullable void (^)(NSURL * _Nullable,  NSURLResponse * _Nullable,  NSError * _Nullable))completionHandler {

    self = [super init];

    if (self) {
        __unsafe_unretained DownloadOperation *weakSelf = self;
        // use weak self to prevent retain cycle
        _task = [[NSURLSession sharedSession] downloadTaskWithURL:downloadTaskURL
                                                completionHandler:^(NSURL * _Nullable localURL, NSURLResponse * _Nullable response, NSError * _Nullable error) {

            /*
             if there is a custom completionHandler defined,
             pass the result gotten in downloadTask's completionHandler to the
             custom completionHandler
             */
            if (completionHandler) {
                completionHandler(localURL, response, error);
            }

            /*
             set the operation state to finished once
             the download task is completed or have error
             */
            weakSelf.state = kFinished;
        }];
    }

    return self;
}

- (void)start {
    /*
     if the operation or queue got cancelled even
     before the operation has started, set the
     operation state to finished and return
     */
    if (self.cancelled) {
        self.state = kFinished;
        return;
    }

    // set the state to executing
    self.state = kExecuting;

    NSLog(@"downloading %@", self.task.originalRequest.URL.absoluteString);

    // start the downloading
    [self.task resume];
}

-(void)cancel {
    [super cancel];

    // cancel the downloading
    [self.task cancel];
}

@end

正如 Scott Thompson 在评论中指出的那样,用于 weakSelf 变量的正确关键字是 __weak,而不是 __unsafe_unretained