Using NSOperation is curiously slow
I'm dealing with a problem here. There's this view that I use to see thumbnails of a document in my app. Since loading the thumbnails slowed the main thread, I looked for workarounds and ended up on d开发者_JAVA技巧oing an NSOperation for the thumbnail creation task.
I'm displaying a view with empty thumbnail frames and the corresponding activity indicator to tell the user "hold on, they're on their way". But this it's taking so long that I'm thinking of putting some elevator music to make the wait more pleasant x_X.
I have this NSOperationQueue set with 10 max concurrent operations. Setting that helped a little with the loading part, but just a little tiny little. Loading a single thumb it still taking like 6 seconds and that's and the weird thing it's that loading 10 takes the same. The following code is the operation itself
@class ThumbnailView;
@protocol LoadThumbnailOperationDelegate;
@interface LoadThumbnailOperation : NSOperation {
NSString *key;
id<LoadThumbnailOperationDelegate> delegate;
@private
CGSize _size;
CGPDFDocumentRef _docRef;
UIImage * _image;
NSInteger _page;
}
@property (nonatomic,retain) NSString * key;
@property (nonatomic,assign) id<LoadThumbnailOperationDelegate>delegate;
-(id)initWithPage:(NSInteger)page operationKey:(NSString *)opKey fromDocRef:(CGPDFDocumentRef)docRef size:(CGSize)size delegate:(id<LoadThumbnailOperationDelegate>)aDelegate;
-(NSInteger)getPage ;
@protocol LoadThumbnailOperationDelegate <NSObject>
-(void)operation:(LoadThumbnailOperation*)operation finishedLoadingThumbnail:(UIImage*)image;
@end
@interface LoadThumbnailOperation (private)
-(UIImage*)makeThumbnailForPage:(NSInteger)page;
@end
@implementation LoadThumbnailOperation
@synthesize key;
@synthesize delegate;
-(id)initWithPage:(NSInteger)page operationKey:(NSString *)opKey fromDocRef:(CGPDFDocumentRef)docRef size:(CGSize)size delegate:(id<LoadThumbnailOperationDelegate>)aDelegate{
self = [super init];
if (self) {
self.key = opKey;
_docRef = docRef;
CGPDFDocumentRetain(_docRef);
_size = size;
_page = page;
self.delegate = delegate;
}
return self;
}
-(void)main {
#if DEBUG
NSLog( @"LoadThumbnailOperaiton.m -> main key:%@",key);
#endif
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
if (![self isCancelled]) {
_image = [self makeThumbnailForPage:_page];
[_image retain];
}
if(![self isCancelled]){
if ([delegate respondsToSelector:@selector(operation:finishedLoadingThumbnail:)]) {
[delegate operation:self finishedLoadingThumbnail:_image];
}
}
[pool release];
}
-(void)dealloc {
[key release];
CGPDFDocumentRelease(_docRef);
[_image release];
[super dealloc];
}
#pragma mark -
#pragma mark graphics
-(UIImage*) makeThumbnailForPage :(NSInteger) page {
#if DEBUG
NSLog( @"LoadThumbnailOperaiton.m -> makeThumbnailForPage:%d",page);
#endif
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(_docRef, page);
if (pdfPage !=NULL){
CGPDFPageRetain(pdfPage);
}else {
NSAssert (pdfPage==NULL,@"pdf page NULL");
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
_size.width,
_size.height,
8, /* bits per component*/
_size.width * 4, /* bytes per row */
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextClipToRect(context, CGRectMake(0, 0, _size.width,_size.height));
CGRect pdfPageRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
CGRect contextRect = CGContextGetClipBoundingBox(context);
CGAffineTransform transform =aspectFit(pdfPageRect, contextRect);
CGContextConcatCTM(context, transform);
CGContextDrawPDFPage(context, pdfPage);
/*
create bitmap context
*/
CGImageRef image = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *uiImage = [[UIImage alloc]initWithCGImage:image];
// clean up
[uiImage autorelease];
CGImageRelease(image);
CGPDFPageRelease(pdfPage);
return uiImage;
}
-(NSInteger)getPage {
return _page;
}
@end
This is the delegate method on the view controller that adds the images that have been loaded
NOTE: this app is for iPad only
Thank you in advance for your help
Well, I found a workaround to this issue. I solved it by checking whether the delegate call was done on the main thread or not. Apparently, the operations were done pretty fast, but the delegate call was not because it was not done on the main thread. So make sure you are making your delegate call on that thread by using the -[NSObject performSelectorOnMainThread:] method.
This still might not be fast for some customers. I ended up generating pdf thumbnails with automator and adding them to the app bundle and loading them from disk instead. Is much more faster than generating them on the ipad.
NOTE: this app is for iPad only
The iPad has one core.
Not only will cranking the concurrency up to 11 not make things any faster, it'll actually make it considerably slower due to bus contention, I/O limitations, and cache misses.
The only thing preventing your app from running slower is NSOperation; it is automatically throttling concurrency to prevent exactly the problems I mention from degrading performance far far worse than you are currently seeing.
精彩评论