【问题标题】:iphone - Displaying a big image in a small frame UIImageView still costs much memory?iphone - 在小框架中显示大图像 UIImageView 仍然需要很多内存吗?
【发布时间】:2011-10-09 17:16:00
【问题描述】:

我有几张大小约为 1.5 MB 的大图片。我用 UIViewContentModeScaleFit 在 UIImageView 中显示它们中的每一个。

UIImageViews 的帧大小只有 150 * 150。

我的问题是

我知道如果我全屏显示大图像,内存会大大增加。

但是如果它们在小的 UIImageView 中,它们仍然会消耗内存吗?

谢谢

【问题讨论】:

    标签: iphone memory uiimageview


    【解决方案1】:
    #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;
    }
    

    【讨论】:

      【解决方案2】:

      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 行。

      【讨论】:

      • 感谢您的代码。我想知道您的代码是否处理正确的比率?
      • 不,它没有;我错过了 UIViewContentModeScaleAspectFit 线索。我会马上修复代码。
      • 下面两行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;吗??
      【解决方案3】:

      是的。您应该创建图像的缩小版本,将它们缓存到磁盘的某个位置,在图像视图中使用小版本并卸载大版本。

      【讨论】:

        【解决方案4】:

        就帧缓冲内存(即用于显示像素的内存)而言,它们不会花费太多,但在内存中保存未缩放的图像仍然会产生相当大的成本。如果 1.5MB 是压缩后的大小。这可能是 high(iOS 使用大约 4 个字节 * 宽度 * 每个图像的高度来存储未压缩的 UIImages)。当你的应用程序进入后台时,这些图像与内核将执行的自动内存管理交互也很差 - 当内存压力发生时,后备存储会被释放,但后备图像不会。

        如果这是一个问题,最好的解决方法(如果您应该自己调整大小,存储较小的版本,然后发布较大的版本)是使用 VM Tracker 通过 Instruments 运行您的应用程序。如果存储图像的区域过多,您将能够诊断它们并选择适当的解决方案。您可能想查看有关 iOS 内存管理的 WWDC 2011 会议,其中详细介绍了图像内存使用情况,包括使用 Instruments 来查找问题。

        与往常一样,在优化之前先配置文件(或者,Apple 可能会说,仪器)!

        【讨论】:

        • 谢谢。您能否向我推荐任何用于缩小图像的开源助手库?
        • 还有,我在哪里可以找到 VM Tracker?
        • @Jackson:不需要开源帮助程序库,Core Graphics 中大约有十行。为了把它放在可以正确显示的地方,我会匆忙在我的答案中附加一些东西。
        • 对于 VM Tracker,创建一个新的空白 Instruments 文档,然后从库窗格(您可以从工具栏打开)拖入“VM Tracker”。
        • @Adam Wright,谢谢亚当。如果我没有理解错,VM Tracker 会跟踪虚拟内存的东西吗?我应该更关心真实内存吗?
        猜你喜欢
        • 2011-07-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-26
        • 1970-01-01
        • 2011-08-05
        • 2015-03-21
        • 2015-09-06
        相关资源
        最近更新 更多