我使用NSOperation和NSOperationQueue执行下载图像。每个操作都保留一个StoreThumbRequest对象,该对象封装所有特定于请求的数据,包括目标视图,等待图像。同时,此视图保留NSOperation对象以在重用或取消分配期间取消操作。这个保留循环在'取消'和'主要'方法在适当的时候。
我在为maxConcurrentOperationsCount设置限制时发现NSOperation保留在NSOperationQueue中。达到这个限制阻止了主要的'被称为新NSOperations的方法。 NSOperation只有在取消时才会保留在队列中。如果它设法完成它的任务,它将从NSOperationQueue中正常删除。
我的NSOperations是非并发的,没有依赖关系。
有什么建议吗? Thanx提前
#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
据我所知,预计NSOperation不会立即从其NSOperationQueue中删除。但它仍然存在无限期的时间
来自NSOperation取消方法文档
此方法不会强制您的操作代码停止。相反,它会更新对象的内部标志以反映状态的变化。
答案 0 :(得分:1)
我相信这种情况正在发生,因为runloop(当您打算结束操作时没有删除)会使操作保持活动状态。
替换以下行的所有实例:
[[NSRunLoop currentRunLoop] removePort: _port forMode: NSDefaultRunLoopMode];
这一个:
[[NSRunLoop currentRunLoop] removePort: _port forMode: NSDefaultRunLoopMode];
CFRunLoopStop(CFRunLoopGetCurrent());
答案 1 :(得分:1)
事实证明,在'main'方法中,我没有检查操作是否已被取消。我也没有特别考虑从主线程中调用'cancel'方法
我也没有检查'runLoop'是否曾停止过。如果没有附加输入源,它通常会停止,所以我从循环中删除了端口。 我现在改变了使用'runMode:beforeDate:'运行NSRunLoop的方式。
这是更新后的工作代码
#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