Ios 取消后,NSOperation仍保留在NSOperationQueue中
我使用NSOperation和NSOperationQueue下载图像。每个操作都会保留一个StoreThumbRequest对象,该对象封装所有特定于请求的数据,包括目标视图,并等待映像。同时,此视图保留NSOperation对象以在重用或解除分配期间取消操作。在适当的时候,在“取消”和“主要”方法中小心地打破这个保留循环 我发现一个NSOperation在设置其maxConcurrentOperationsCount的限制时仍保留在NSOperationQueue中。达到此限制将阻止调用新操作的“main”方法。NSOperation仅在取消时才保留在队列中。如果它成功地完成了任务,它将从其NSOperationQueue中优雅地删除 我的操作是非并发的,没有依赖关系 有什么建议吗?先发制人Ios 取消后,NSOperation仍保留在NSOperationQueue中,ios,nsurlconnection,nsoperation,nsoperationqueue,nsrunloop,Ios,Nsurlconnection,Nsoperation,Nsoperationqueue,Nsrunloop,我使用NSOperation和NSOperationQueue下载图像。每个操作都会保留一个StoreThumbRequest对象,该对象封装所有特定于请求的数据,包括目标视图,并等待映像。同时,此视图保留NSOperation对象以在重用或解除分配期间取消操作。在适当的时候,在“取消”和“主要”方法中小心地打破这个保留循环 我发现一个NSOperation在设置其maxConcurrentOperationsCount的限制时仍保留在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