Ios 取消后,NSOperation仍保留在NSOperationQueue中

Ios 取消后,NSOperation仍保留在NSOperationQueue中,ios,nsurlconnection,nsoperation,nsoperationqueue,nsrunloop,Ios,Nsurlconnection,Nsoperation,Nsoperationqueue,Nsrunloop,我使用NSOperation和NSOperationQueue下载图像。每个操作都会保留一个StoreThumbRequest对象,该对象封装所有特定于请求的数据,包括目标视图,并等待映像。同时,此视图保留NSOperation对象以在重用或解除分配期间取消操作。在适当的时候,在“取消”和“主要”方法中小心地打破这个保留循环 我发现一个NSOperation在设置其maxConcurrentOperationsCount的限制时仍保留在NSOperationQueue中。达到此限制将阻止调用新操

我使用NSOperation和NSOperationQueue下载图像。每个操作都会保留一个StoreThumbRequest对象,该对象封装所有特定于请求的数据,包括目标视图,并等待映像。同时,此视图保留NSOperation对象以在重用或解除分配期间取消操作。在适当的时候,在“取消”和“主要”方法中小心地打破这个保留循环

我发现一个NSOperation在设置其maxConcurrentOperationsCount的限制时仍保留在NSOperationQueue中。达到此限制将阻止调用新操作的“main”方法。NSOperation仅在取消时才保留在队列中。如果它成功地完成了任务,它将从其NSOperationQueue中优雅地删除

我的操作是非并发的,没有依赖关系

有什么建议吗?先发制人

#import "StoreThumbLoadOperation.h"
#import "StoreThumbCache.h"

@interface StoreThumbLoadOperation () <NSURLConnectionDelegate> {
    StoreThumbRequest *_request;
    NSMutableData *_downloadedData;
    NSURLConnection *_connection;
    NSPort *_port;
}

@end

@implementation StoreThumbLoadOperation

-(id)initWithRequest: (StoreThumbRequest *)request
{
    NSParameterAssert(request);
    self = [super init];
    if (self) {
        _request = request;
    }
    return self;
}

-(void)main
{
    NSURL *url = ...;

    NSURLRequest *request = [NSURLRequest requestWithURL: url];
    _connection = [[NSURLConnection alloc] initWithRequest: request delegate: self startImmediately: NO];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    _port = [NSMachPort port];
    [runLoop addPort: _port forMode: NSDefaultRunLoopMode];
    [_connection scheduleInRunLoop: runLoop forMode: NSDefaultRunLoopMode];
    [_connection start];
    [runLoop run];
}

-(void)cancel
{
    [super cancel];

    [_connection unscheduleFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
    [_connection cancel];
    _request.thumbView.operation = nil; //break retain loop
    [[NSRunLoop currentRunLoop] removePort: _port forMode: NSDefaultRunLoopMode];
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    _downloadedData = [NSMutableData new];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [_downloadedData appendData: data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    if (_connection == connection) {
        NSFileManager *fileManager = [NSFileManager new];
        NSString *pathForDownloadedThumb = [[StoreThumbCache sharedCache] thumbPathOnRequest: _request];
        NSString *pathForContainigDirectory = [pathForDownloadedThumb stringByDeletingLastPathComponent];
        if (! [fileManager fileExistsAtPath: pathForContainigDirectory]) {
            //create a directory if required
            [fileManager createDirectoryAtPath: pathForContainigDirectory withIntermediateDirectories: YES attributes: nil error: nil];
        }

        if (! self.isCancelled) {
            [_downloadedData writeToFile: pathForDownloadedThumb atomically: YES];
            UIImage *image = [UIImage imageWithContentsOfFile: pathForDownloadedThumb];

            //an image may be empty
            if (image) {
                if (! self.isCancelled) {
                    [[StoreThumbCache sharedCache] setThumbImage: image forKey: _request.cacheKey];

                    if (_request.targetTag == _request.thumbView.tag) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            _request.thumbView.image = image;
                        });
                    }
                }
            }
             _request.thumbView.operation = nil; //break retain loop
            [[NSRunLoop currentRunLoop] removePort: _port forMode: NSDefaultRunLoopMode];
        }
    }
}


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    if (error.code != NSURLErrorCancelled) {
        #ifdef DEBUG
        NSLog(@"failed downloading thumb for name: %@ with error %@", _request.thumbName, error.localizedDescription);
        #endif

        _request.thumbView.operation = nil;
        [[NSRunLoop currentRunLoop] removePort: _port forMode: NSDefaultRunLoopMode];
    }
}

@end
#导入“StoreThumbLoadOperation.h”
#导入“StoreThumbCache.h”
@接口StoreThumbLoadOperation(){
StoreThumbRequest*\u请求;
NSMutableData*_下载数据;
NSURLConnection*_连接;
NSPort*\u端口;
}
@结束
@StoreThumbLoadOperation的实现
-(id)initWithRequest:(StoreThumbRequest*)请求
{
NSParameterAssert(请求);
self=[super init];
如果(自我){
_请求=请求;
}
回归自我;
}
-(无效)主要
{
NSURL*url=。。。;
NSURLRequest*request=[nsurlRequestRequestWithURL:url];
_连接=[[NSURLConnection alloc]initWithRequest:请求委托:自启动中间:否];
NSRunLoop*runLoop=[NSRunLoop currentlunloop];
_端口=[NSMachPort端口];
[runLoop addPort:_PortFormode:NSDefaultRunLoopMode];
[_连接调度InRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[_连接启动];
[运行循环运行];
}
-(作废)取消
{
[超级取消];
[_连接非计划性运行循环:[NSRunLoop currentRunLoop]forMode:NSDefaultRunLoopMode];
[_连接取消];
_request.thumbView.operation=nil;//中断保留循环
[[NSRunLoop currentlunloop]removePort:_portformode:NSDefaultRunLoopMode];
}
#pragma标记-NSURLConnectionLegate
-(void)连接:(NSURLConnection*)连接DidReceiverResponse:(NSURResponse*)响应
{
_下载数据=[NSMutableData新];
}
-(void)连接:(NSURLConnection*)连接didReceiveData:(NSData*)数据
{
[_下载数据appendData:data];
}
-(无效)连接IDFinishLoading:(NSURLConnection*)连接
{
if(_connection==连接){
NSFileManager*fileManager=[NSFileManager新建];
NSString*pathForDownloadedThumb=[[StoreThumbCache sharedCache]ThumbPathInRequest:_请求];
NSString*pathForContainigDirectory=[pathForDownloadedThumb stringByDeletingLastPathComponent];
如果(![fileManager fileExistsAtPath:PathForContaineigDirectory]){
//如果需要,创建一个目录
[fileManager createDirectoryAtPath:pathForContainigDirectory with IntermediateDirectory:YES属性:nil错误:nil];
}
如果(!self.isCancelled){
[_DownloadedDataWriteFile:pathForDownloadedThumb原子性:是];
UIImage*image=[UIImage imageWithContentsOfFile:PathForDownloadedTumb];
//图像可能是空的
如果(图像){
如果(!self.isCancelled){
[[StoreThumbCache sharedCache]setThumbImage:image forKey:_request.cacheKey];
if(_request.targetTag==_request.thumbView.tag){
dispatch\u async(dispatch\u get\u main\u queue()^{
_request.thumbView.image=图像;
});
}
}
}
_request.thumbView.operation=nil;//中断保留循环
[[NSRunLoop currentlunloop]removePort:_portformode:NSDefaultRunLoopMode];
}
}
}
-(无效)连接:(NSURLConnection*)连接失败错误:(NSError*)错误
{
if(error.code!=nsurerrorcancelled){
#ifdef调试
NSLog(@“下载名称%@的thumb失败,出现错误%@”,_request.thumbName,error.localizedDescription);
#恩迪夫
_request.thumbView.operation=nil;
[[NSRunLoop currentlunloop]removePort:_portformode:NSDefaultRunLoopMode];
}
}
@结束
我理解,NSOperation不会立即从其NSOperationQueue中删除。但它会无限期地留在那里

从NSOperation取消方法文档

此方法不会强制停止操作代码。相反,它会更新对象的内部标志以反映状态的变化


我相信这是因为runloop(在您打算结束操作时没有删除)使操作保持活动状态

替换以下行的所有实例:

[[NSRunLoop currentRunLoop] removePort: _port forMode: NSDefaultRunLoopMode];
关于这一点:

[[NSRunLoop currentRunLoop] removePort: _port forMode: NSDefaultRunLoopMode];
CFRunLoopStop(CFRunLoopGetCurrent());

事实证明,在“main”方法中,我没有检查操作是否已经取消。我也没有特别考虑从主线程调用“cancel”方法

我也没有检查“runLoop”是否停止过。如果没有连接输入源,它通常会停止,因此我正在从循环中删除端口。 现在,我使用“runMode:beforeDate:”更改了运行nsrunlop的方式

这是更新后的工作代码

#define STORE_THUMB_LOAD_OPERATION_RETURN_IF_CANCELLED() \
if (self.cancelled) {\
    [self internalComplete]; \
    return; \
}

@interface StoreThumbLoadOperation () <NSURLConnectionDelegate>
@property (strong, nonatomic) StoreThumbRequest *request;
@property (strong, nonatomic) NSMutableData *downloadedData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, nonatomic) NSPort *port;
@property (strong, nonatomic) NSRunLoop *runLoop;
@property (strong, nonatomic) NSThread *thread;
@property (assign, nonatomic) unsigned long long existingOnDiskThumbWeightInBytes;
@property (assign, nonatomic) BOOL isCompleted;
@property (assign, nonatomic) BOOL needsStop;
@end

@implementation StoreThumbLoadOperation

-(id)initWithRequest: (StoreThumbRequest *)request existingCachedImageSize:(unsigned long long)bytes
{
    NSParameterAssert(request);
    self = [super init];
    if (self) {
        self.request = request;
        self.existingOnDiskThumbWeightInBytes = bytes;
    }
    return self;
}

-(void)main
{
    // do not call super for optimizations
    //[super main];

    STORE_THUMB_LOAD_OPERATION_RETURN_IF_CANCELLED();

    @autoreleasepool {
        NSURL *url = ...;

        NSURLRequest *request = [NSURLRequest requestWithCredentialsFromUrl:url];
        self.connection = [[NSURLConnection alloc] initWithRequest: request delegate: self startImmediately: NO];

        self.runLoop = [NSRunLoop currentRunLoop];
        self.port = [NSMachPort port];
        self.thread = [NSThread currentThread];  // <- retain the thread

        [self.runLoop addPort: self.port forMode: NSDefaultRunLoopMode];
        [self.connection scheduleInRunLoop: self.runLoop forMode: NSDefaultRunLoopMode];

        [self.connection start];

        // will run the loop until the operation is not cancelled or the image is not downloaded
        NSTimeInterval stepLength = 0.1;
        NSDate *future = [NSDate dateWithTimeIntervalSinceNow:stepLength];
        while (! self.needsStop && [self.runLoop runMode:NSDefaultRunLoopMode beforeDate:future]) {
            future = [future dateByAddingTimeInterval:stepLength];
        }

        // operation is cancelled, or the image is downloaded
        self.isCompleted = YES;
        [self internalComplete];
    }
}

-(void)cancel {
    [super cancel];
    STORE_THUMB_LOAD_OPERATION_LOG_STATUS(@"cancelled");
    [self setNeedsStopOnPrivateThread];
}

- (BOOL)isFinished {
    // the operation must become completed to be removed from the queue
    return self.isCompleted || self.isCancelled;
}

#pragma mark - privates

- (void)setNeedsStopOnPrivateThread {
  // if self.thread is not nil, that the 'main' method was already called. if not, than the main thread cancelled the operation before it started
  if (! self.thread || [NSThread currentThread] == self.thread) {
     [self internalComplete];
  } else {
     [self performSelector:@selector(internalComplete) onThread:self.thread withObject:nil waitUntilDone:NO];
  }
}

- (void)internalComplete {
    [self cleanUp];
    self.needsStop = YES; // <-- will break the 'while' loop
}

- (void)cleanUp {
    [self.connection unscheduleFromRunLoop: self.runLoop forMode: NSDefaultRunLoopMode];
    [self.connection cancel];
    self.connection = nil;

    //break retain loop
    self.request.thumbView.operation = nil;

    [self.runLoop removePort: self.port forMode: NSDefaultRunLoopMode];
    self.port = nil;
    self.request = nil;
    self.downloadedData = nil;
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.downloadedData = [NSMutableData new];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.downloadedData appendData: data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    if (self.connection != connection)
        return;

    STORE_THUMB_LOAD_OPERATION_LOG_STATUS(@"entered connection finished");

    //create a directory if required

    STORE_THUMB_LOAD_OPERATION_RETURN_IF_CANCELLED();

    // wright the image to the file

    STORE_THUMB_LOAD_OPERATION_RETURN_IF_CANCELLED();

    // create the image from the file
    UIImage *image = [UIImage imageWithContentsOfFile:...];

    STORE_THUMB_LOAD_OPERATION_RETURN_IF_CANCELLED();

    if (image) {
        // cache the image

        STORE_THUMB_LOAD_OPERATION_RETURN_IF_CANCELLED();

        // update UI on the main thread
        if (self.request.targetTag == self.request.thumbView.tag) {
            StoreThumbView *targetView = self.request.thumbView;
            dispatch_async(dispatch_get_main_queue(), ^{
                targetView.image = image;
            });
        }
    }

    [self setNeedsStopOnPrivateThread];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    if (error.code != NSURLErrorCancelled) {            
        [self cancel];
    }
}

@end
#定义存储_拇指_加载_操作_返回_如果_取消()\
如果(自行取消){\
[自我完成]\
返回\
}
@接口StoreThumbLoadOperation()
@属性(强,非原子)StoreThumbRequest*请求;
@属性(强,非原子)NSMutableData*下载数据;
@属性(强,非原子)NSURLConnection*连接;
@属性(强、非原子)NSPort*端口;
@属性(强,非原子)NSRunLoop*runLoop;
@属性(强,非原子)NSThread*线程;
@属性(赋值,非原子)unsig