JBx*_*JBx 12 iphone lazy-loading exc-bad-access uiimage cgimage
我的程序显示一个水平滚动表面,从左到右平铺UIImageViews.代码在UI线程上运行,以确保新显示的UIImageViews分配了一个新加载的UIImage.加载发生在后台线程上.
一切都很好,除了每个图像变得可见时都有口吃.起初我以为我的后台工作者在UI线程中锁定了一些内容.我花了很多时间查看它,并最终意识到UIImage在UI线程第一次变得可见时正在做一些额外的延迟处理.这让我很困惑,因为我的工作线程有明确的解压缩JPEG数据的代码.
无论如何,在预感上,我写了一些代码,以渲染到后台线程上的临时图形上下文中 - 当然,口吃也消失了.UIImage现在正在我的工作线程上预先加载.到现在为止还挺好.
问题是我的新"强力懒惰的图像"方法是不可靠的.它会导致间歇性的EXC_BAD_ACCESS.我不知道UIImage在幕后实际上在做什么.也许它正在解压缩JPEG数据.无论如何,方法是:
+ (void)forceLazyLoadOfImage: (UIImage*)image
{
CGImageRef imgRef = image.CGImage;
CGFloat currentWidth = CGImageGetWidth(imgRef);
CGFloat currentHeight = CGImageGetHeight(imgRef);
CGRect bounds = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
CGAffineTransform transform = CGAffineTransformIdentity;
CGFloat scaleRatioX = bounds.size.width / currentWidth;
CGFloat scaleRatioY = bounds.size.height / currentHeight;
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleRatioX, -scaleRatioY);
CGContextTranslateCTM(context, 0, -currentHeight);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, currentWidth, currentHeight), imgRef);
UIGraphicsEndImageContext();
}
Run Code Online (Sandbox Code Playgroud)
并且EXC_BAD_ACCESS发生在CGContextDrawImage行上.问题1:我是否允许在UI线程以外的线程上执行此操作?问题2:什么是UIImage实际上"预加载"?问题3:解决这个问题的官方方法是什么?
感谢阅读所有这些,任何建议将不胜感激!
jas*_*mer 22
我有同样的口吃问题,在一些帮助下我找到了正确的解决方案:iOS中非懒惰的图像加载
需要提到的两件重要事情:
-
CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
Run Code Online (Sandbox Code Playgroud)
我在这里发布了一个示例项目:SwapTest,它与Apples的Photos应用程序具有相同的性能,用于加载/显示图像.
Hlu*_*ung 11
我使用@jasamer的SwapTest UIImage类别来强制加载我的大型UIImage(大约3000x2100像素)在一个工作线程中(使用NSOperationQueue).这可以减少将图像设置为UIImageView时的断续时间,使其达到可接受的值(在iPad1上约为0.5秒).
这是SwapTest UIImage类别......再次感谢@jasamer :)
UIImage + ImmediateLoading.h文件
@interface UIImage (UIImage_ImmediateLoading)
- (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path;
+ (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path;
@end
Run Code Online (Sandbox Code Playgroud)
UIImage + ImmediateLoading.m文件
#import "UIImage+ImmediateLoading.h"
@implementation UIImage (UIImage_ImmediateLoading)
+ (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path {
return [[[UIImage alloc] initImmediateLoadWithContentsOfFile: path] autorelease];
}
- (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path {
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
CGImageRef imageRef = [image CGImage];
CGRect rect = CGRectMake(0.f, 0.f, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
CGContextRef bitmapContext = CGBitmapContextCreate(NULL,
rect.size.width,
rect.size.height,
CGImageGetBitsPerComponent(imageRef),
CGImageGetBytesPerRow(imageRef),
CGImageGetColorSpace(imageRef),
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little
);
//kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little are the bit flags required so that the main thread doesn't have any conversions to do.
CGContextDrawImage(bitmapContext, rect, imageRef);
CGImageRef decompressedImageRef = CGBitmapContextCreateImage(bitmapContext);
UIImage* decompressedImage = [[UIImage alloc] initWithCGImage: decompressedImageRef];
CGImageRelease(decompressedImageRef);
CGContextRelease(bitmapContext);
[image release];
return decompressedImage;
}
@end
Run Code Online (Sandbox Code Playgroud)
这就是我创建NSOpeationQueue并在主线程上设置图像的方法......
// Loads low-res UIImage at a given index and start loading a hi-res one in background.
// After finish loading, set the hi-res image into UIImageView. Remember, we need to
// update UI "on main thread" otherwise its result will be unpredictable.
-(void)loadPageAtIndex:(int)index {
prevPage = index;
//load low-res
imageViewForZoom.image = [images objectAtIndex:index];
//load hi-res on another thread
[operationQueue cancelAllOperations];
NSInvocationOperation *operation = [NSInvocationOperation alloc];
filePath = [imagesHD objectAtIndex:index];
operation = [operation initWithTarget:self selector:@selector(loadHiResImage:) object:[imagesHD objectAtIndex:index]];
[operationQueue addOperation:operation];
[operation release];
operation = nil;
}
// background thread
-(void)loadHiResImage:(NSString*)file {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"loading");
// This doesn't load the image.
//UIImage *hiRes = [UIImage imageNamed:file];
// Loads UIImage. There is no UI updating so it should be thread-safe.
UIImage *hiRes = [[UIImage alloc] initImmediateLoadWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType: nil]];
[imageViewForZoom performSelectorOnMainThread:@selector(setImage:) withObject:hiRes waitUntilDone:NO];
[hiRes release];
NSLog(@"loaded");
[pool release];
}
Run Code Online (Sandbox Code Playgroud)
这些UIGraphics*
方法只能从主线程调用.它们可能是你麻烦的根源.
你可以UIGraphicsBeginImageContext()
用一个电话代替CGBitmapContextCreate()
; 它需要更多一些(你需要创建一个颜色空间,找出正确大小的缓冲区来创建,并自己分配).CG*
从不同的线程运行这些方法很好.
我不知道你是如何初始化的UIImage,但如果你有这样做imageNamed:
还是initWithFile:
那么你也许可以迫使它自己加载数据,然后调用加载initWithData:
.口吃可能是由于懒惰的文件I/O,因此使用数据对象初始化它不会给它提供从文件读取的选项.
归档时间: |
|
查看次数: |
11040 次 |
最近记录: |