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
。
我正在尝试创建 NSOperation 的 DownloadOperation 子类来异步下载数据。在我尝试添加取消支持之前,一切似乎都运行良好。基本上,操作的 NSURLSessionDownloadTask 的完成处理程序似乎在操作释放后被调用。它会在 weakSelf.state = kFinished
.
完整的示例项目在这里: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
。