#objective-c #macos
Вопрос:
Я пытаюсь создать подкласс DownloadOperation для NSOperation для асинхронной загрузки данных. Казалось, все работало нормально, пока я не попытался добавить поддержку отмены. По сути, обработчик завершения NSURLSessionDownloadTask операции, похоже, вызывается после того, как операция была выпущена. Произойдет сбой с EXC_BAD_ACCESS в строке 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
Комментарии:
1. Вы записываете
self
as__unsafe_unretained
, и именно поэтому в этом имени есть «небезопасный». ВашDownloadOperation
, по-видимому, удален до завершения загрузки. Вы можете либо 1) убедиться, что он живет достаточно долгоdownloadTaskWithURL
для завершения, либо 2) захватить self в качестве слабой ссылки и проверить, равен ли он нулю, прежде чем использовать его.2. Большое спасибо! Я попытался прочитать
weak
versusunsafe_unretained
, но я не могу точно сказать, есть ли когда-либо какая-либо причина использовать последнее больше, если вы не нацелены на действительно старую систему. Большая часть документации, которую вы найдете в Интернете, так или иначе устарела.3. При
weak
использовании ссылки, когда объект, на который она ссылается, удаляется,weak
ссылка будет установлена наnil
. Затем вы можете проверить ссылку (например, в вашем блоке), чтобы убедиться, что этоnil
так, прежде чем использовать ее. Использованиеunsafe_unretained
указателя сохранит его значение, даже если объект будет удален. Использованиеweak
ссылки приводит к небольшому снижению производительности при удалении объекта. Если вам все равно, будет ли объект удален из-под вас, и вы не хотитеweak
снижения производительности ссылки, вы можете использоватьunsafe_unretained
.4. Для получения более современной документации (написанной в контексте Swift) вы можете посмотреть: Автоматический подсчет ссылок . Просто поймите, что в Swift
unsafe_unretained
вызываетсяunowned
5. весь пример забавный, потому что он имитирует то, что уже предлагает completionHandler.
Ответ №1:
Как указано в комментариях Скотта Томпсона, правильным ключевым словом для переменной weakSelf является __weak
, not __unsafe_unretained
.