【发布时间】:2011-10-09 17:16:00
【问题描述】:
我有几张大小约为 1.5 MB 的大图片。我用 UIViewContentModeScaleFit 在 UIImageView 中显示它们中的每一个。
UIImageViews 的帧大小只有 150 * 150。
我的问题是
我知道如果我全屏显示大图像,内存会大大增加。
但是如果它们在小的 UIImageView 中,它们仍然会消耗内存吗?
谢谢
【问题讨论】:
标签: iphone memory uiimageview
我有几张大小约为 1.5 MB 的大图片。我用 UIViewContentModeScaleFit 在 UIImageView 中显示它们中的每一个。
UIImageViews 的帧大小只有 150 * 150。
我的问题是
我知道如果我全屏显示大图像,内存会大大增加。
但是如果它们在小的 UIImageView 中,它们仍然会消耗内存吗?
谢谢
【问题讨论】:
标签: iphone memory uiimageview
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
+ (UIImage *)resizeImage:(UIImage *)image toResolution:(int)resolution {
NSData *imageData = UIImagePNGRepresentation(image);
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef) @{
(id) kCGImageSourceCreateThumbnailWithTransform : @YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(id) kCGImageSourceThumbnailMaxPixelSize : @(resolution)
};
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
UIImage *img = [[UIImage alloc]initWithCGImage:thumbnail];
return img;
}
【讨论】:
UIImages 和 UIImageViews 是不同的东西。每个UIImageView 都知道显示其关联UIImage 的大小。 UIImage 不知道如何显示自己或如何将其用于显示,因此仅更改UIImageView 的大小的行为对UIImage 没有影响。因此它对总内存使用没有影响。
您可能想要做的是使用 Core Graphics 获取 UIImage 并将其生成 150x150 版本作为新的 UIImage,然后将其推送到 UIImageView。
要执行缩放,类似以下的代码(在我输入时编写,因此未彻底检查)应该可以完成这项工作:
#include <math.h>
- (UIImage *)scaledImageForImage:(UIImage *)srcImage toSize:(CGSize)maxSize
{
// pick the target dimensions, as though applying
// UIViewContentModeScaleAspectFit; seed some values first
CGSize sizeOfImage = [srcImage size];
CGSize targetSize; // to store the output size
// logic here: we're going to scale so as to apply some multiplier
// to both the width and height of the input image. That multiplier
// is either going to make the source width fill the output width or
// it's going to make the source height fill the output height. Of the
// two possibilities, we want the smaller one, since the larger will
// make the other axis too large
if(maxSize.width / sizeOfImage.width < maxSize.height / sizeOfImage.height)
{
// we'll letter box then; scaling width to fill width, since
// that's the smaller scale of the two possibilities
targetSize.width = maxSize.width;
// height is the original height adjusted proportionally
// to match the proportional adjustment in width
targetSize.height =
(maxSize.width / sizeOfImage.width) * sizeOfImage.height;
}
else
{
// basically the same as the above, except that we pillar box
targetSize.height = maxSize.height;
targetSize.width =
(maxSize.height / sizeOfImage.height) * sizeOfImage.width;
}
// images can be integral sizes only, so round up
// the target size and width, then construct a target
// rect that centres the output image within that size;
// this all ensures sub-pixel accuracy
CGRect targetRect;
// store the original target size and round up the original
targetRect.size = targetSize;
targetSize.width = ceilf(targetSize.width);
targetSize.height = ceilf(targetSize.height);
// work out how to centre the source image within the integral-sized
// output image
targetRect.origin.x = (targetSize.width - targetRect.size.height) * 0.5f;
targetRect.origin.y = (targetSize.height - targetRect.size.width) * 0.5f;
// now create a CGContext to draw to, draw the image to it suitably
// scaled and positioned, and turn the thing into a UIImage
// get a suitable CoreGraphics context to draw to, in RGBA;
// I'm assuming iOS 4 or later here, to save some manual memory
// management.
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(
NULL,
targetSize.width, targetSize.height,
8, targetSize.width * 4,
colourSpace,
kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colourSpace);
// clear the context, since it may currently contain anything.
CGContextClearRect(context,
CGRectMake(0.0f, 0.0f, targetSize.width, targetSize.height));
// draw the given image to the newly created context
CGContextDrawImage(context, targetRect, [srcImage CGImage]);
// get an image from the CG context, wrapping it as a UIImage
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage *returnImage = [UIImage imageWithCGImage:cgImage];
// clean up
CGContextRelease(context);
CGImageRelease(cgImage);
return returnImage;
}
显然,我通过大量评论使它看起来很复杂,但实际上只有 23 行。
【讨论】:
targetRect.origin.x = (targetSize.width - targetRect.size.height) * 0.5f; targetRect.origin.y = (targetSize.height - targetRect.size.width) * 0.5f;不应该是targetRect.origin.x = (maxSize.width - targetSize.width) * 0.5f; targetRect.origin.y = (maxSize.height - targetSize.height) * 0.5f;吗??
是的。您应该创建图像的缩小版本,将它们缓存到磁盘的某个位置,在图像视图中使用小版本并卸载大版本。
【讨论】:
就帧缓冲内存(即用于显示像素的内存)而言,它们不会花费太多,但在内存中保存未缩放的图像仍然会产生相当大的成本。如果 1.5MB 是压缩后的大小。这可能是 high(iOS 使用大约 4 个字节 * 宽度 * 每个图像的高度来存储未压缩的 UIImages)。当你的应用程序进入后台时,这些图像与内核将执行的自动内存管理交互也很差 - 当内存压力发生时,后备存储会被释放,但后备图像不会。
如果这是一个问题,最好的解决方法(如果您应该自己调整大小,存储较小的版本,然后发布较大的版本)是使用 VM Tracker 通过 Instruments 运行您的应用程序。如果存储图像的区域过多,您将能够诊断它们并选择适当的解决方案。您可能想查看有关 iOS 内存管理的 WWDC 2011 会议,其中详细介绍了图像内存使用情况,包括使用 Instruments 来查找问题。
与往常一样,在优化之前先配置文件(或者,Apple 可能会说,仪器)!
【讨论】: