CGImage/UIImage延迟装载在UI线导致的口吃
-
06-07-2019 - |
题
我的程序显示水平的滚动面铺有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();
}
和EXC_BAD_ACCESS发生在CGContextDrawImage线。问题1:我允许这样做在线比其他UI线?问题2:什么是UIImage实际上是"预先装载"?问题3:什么是官方的方式来解决这个问题?
感谢您的阅读所有这一切,任何咨询意见将不胜感激!
解决方案
UIGraphics *
方法只能从主线程调用。它们可能是你麻烦的根源。
您可以通过调用 CGBitmapContextCreate()
替换 UIGraphicsBeginImageContext()
;它需要更多一些(你需要创建一个颜色空间,找出正确大小的缓冲区来创建,并自己分配)。 CG *
方法可以从不同的线程运行。
我不确定你是如何初始化UIImage的,但是如果你是用 imageNamed:
或 initWithFile:
来做的话那么你可能会强迫它通过自己加载数据然后调用 initWithData:
来加载。口吃可能是由于懒惰的文件I / O,因此使用数据对象初始化它不会给它提供从文件读取的选项。
其他提示
我已经有同样的口吃的问题,与一些帮助我想出适当的解决方案: 非懒惰的图片加载在iOS
两个重要的事情提一提:
- 不用UIKit方法在工作人员-线。使用CoreGraphics代替。
- 甚至如果你有一个背景线的装载和解的图像,你仍然会有一个小小的口吃如果你使用了错误的位为你CGBitmapContext.这是你的选择必须选择(它仍然是一个有点不清楚我为什么):
-
CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
我已经发布一样的项目: SwapTest, 它具有相同性能作为苹果的照片应用程序的用于装载/显示的图像。
我使用@ 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
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
这就是我创建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];
}
即使我使用数据初始化图像,我也遇到了同样的问题。 (我猜这些数据也是懒散加载的?)我成功地使用以下类别强制解码:
@interface UIImage (Loading)
- (void) forceLoad;
@end
@implementation UIImage (Loading)
- (void) forceLoad
{
const CGImageRef cgImage = [self CGImage];
const int width = CGImageGetWidth(cgImage);
const int height = CGImageGetHeight(cgImage);
const CGColorSpaceRef colorspace = CGImageGetColorSpace(cgImage);
const CGContextRef context = CGBitmapContextCreate(
NULL, /* Where to store the data. NULL = don’t care */
width, height, /* width & height */
8, width * 4, /* bits per component, bytes per row */
colorspace, kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGContextRelease(context);
}
@end