【问题标题】:NSURLSessionDownloadTask in NSOperation crashes on cancelNSOperation 中的 NSURLSessionDownloadTask 在取消时崩溃
【发布时间】:2021-12-11 04:19:38
【问题描述】:

我正在尝试创建 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

【问题讨论】:

  • 正在self 捕获为__unsafe_unretained,这正是该名称中包含“不安全”的原因。您的DownloadOperation 在下载完成之前显然已被删除。您可以 1) 确保它的生命周期足够长以供 downloadTaskWithURL 完成,或者 2) 将 self 作为弱引用并在使用之前检查它是否为 nil。
  • 非常感谢!我尝试阅读weakunsafe_unretained,但我真的不知道是否有任何理由再使用后者,除非您的目标是一个非常旧的系统。您在网上找到的大多数文档都以某种方式过时了。
  • 使用weak 引用,当它引用的对象被删除时,weak 引用将设置为nil。然后,您可以在使用之前检查参考(例如在您的块中)以查看它是否为 nil。使用unsafe_unretained,即使对象被删除,指针也会保持其值。删除对象时,使用weak 引用会产生一点性能开销。我不在乎对象是否从您下方删除,并且您不希望weak 引用的性能受到影响,您可以使用unsafe_unretained
  • 有关更现代的文档(在 Swift 的上下文中编写),您可以查看:Automatic Reference Counting。只要意识到在 Swift 中 unsafe_unretained 被称为 unowned
  • 整个示例很有趣,因为它模仿了 completionHandler 已经提供的功能。

标签: objective-c macos


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多