【发布时间】: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。 -
非常感谢!我尝试阅读
weak与unsafe_unretained,但我真的不知道是否有任何理由再使用后者,除非您的目标是一个非常旧的系统。您在网上找到的大多数文档都以某种方式过时了。 -
使用
weak引用,当它引用的对象被删除时,weak引用将设置为nil。然后,您可以在使用之前检查参考(例如在您的块中)以查看它是否为nil。使用unsafe_unretained,即使对象被删除,指针也会保持其值。删除对象时,使用weak引用会产生一点性能开销。我不在乎对象是否从您下方删除,并且您不希望weak引用的性能受到影响,您可以使用unsafe_unretained。 -
有关更现代的文档(在 Swift 的上下文中编写),您可以查看:Automatic Reference Counting。只要意识到在 Swift 中
unsafe_unretained被称为unowned -
整个示例很有趣,因为它模仿了 completionHandler 已经提供的功能。
标签: objective-c macos