【问题标题】:iPad app crashed due to memory warning while combining/merging two imagesiPad 应用程序在合并/合并两个图像时由于内存警告而崩溃
【发布时间】:2026-01-12 11:40:02
【问题描述】:

我的应用程序在我处理图像时由于大量使用内存而崩溃。

基本上我的整个应用程序依赖于以下方法,其中使用组合图像的逻辑是我用于从包中加载图像的另一种方法。

+ (UIImage*)imageByCombiningImages:(UIImage*)firstImage withImage:(UIImage*)secondImage
{
    @autoreleasepool
    {
        UIImage *image = nil;

        CGSize newImageSize = CGSizeMake(MAX(firstImage.size.width, secondImage.size.width), MAX(firstImage.size.height, secondImage.size.height));
        if (UIGraphicsBeginImageContextWithOptions != NULL)
        {
            UIGraphicsBeginImageContextWithOptions(newImageSize, NO, [[UIScreen mainScreen] scale]);
        }
        else
        {
            UIGraphicsBeginImageContext(newImageSize);
        }

        [firstImage drawAtPoint:CGPointMake(roundf((newImageSize.width-firstImage.size.width)/2), roundf((newImageSize.height-firstImage.size.height)/2))];

        firstImage =nil;
        [secondImage drawAtPoint:CGPointMake(roundf((newImageSize.width-secondImage.size.width)/2), roundf((newImageSize.height-secondImage.size.height)/2))];


        secondImage=nil;

        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image;
    }
}

- (UIImage*) getImage:(NSString*)imagename
{
    NSString *fileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:imagename];
    UIImage *image = [UIImage imageWithContentsOfFile:fileName];
    return image;
}

上面的方法被下面的方法多次调用

- (void) applyMaterialWithValue:(NSDictionary*)dict
{

    @synchronized(self)
    {

        NSString *materialName = [dict valueForKey:@"material"];
        NSString *optionName = [dict valueForKey:@"option"];
        //NSString *preset_Name = [dict valueForKey:@"preset"];
        NSString *name = [dict valueForKey:@"name"];
        if([[GlobalIntVariable getCMFScheme] isEqualToString:@"preset1"])

        {
            if(!isMetalPressed)
            {

                [GlobalIntVariable setCurrentMaterialName:@"Preset 1 Metal Plating"];
            }
            if(!isLeatherPressed)
            {
                [GlobalIntVariable setCurrentPresetName:@"preset1"];

            }

        }
        if([[GlobalIntVariable getCMFScheme] isEqualToString:@"preset2"])

        {

            if(!isMetalPressed)
            {

                [GlobalIntVariable setCurrentMaterialName:@"Preset 2 Metal Plating"];
            }
            if(!isLeatherPressed)
            {
                [GlobalIntVariable setCurrentPresetName:@"preset2"];

            }

        }

        if ([materialName isEqualToString:AREAD4METALCOMPONENET ]|| [materialName isEqualToString:AREA8METALCOMPONENET])
        {
            [GlobalIntVariable setCurrentMaterialName:name];
        }


        if([optionName isEqualToString:OPTION0])
        {
            @autoreleasepool
            {

                UIImage *presetImg1L1Standard = [APP_DEL getImage:[dict valueForKey:@"image1L1Standard"]];
                imgView1Layout1Standard.image = [ImageOperations imageByCombiningImages:imgView1Layout1Standard.image withImage:presetImg1L1Standard];
                imgView1Layout1StandardPSUOn.image = [ImageOperations imageByCombiningImages:imgView1Layout1StandardPSUOn.image withImage:presetImg1L1Standard];
                 presetImg1L1Standard=nil;



                UIImage *presetImg1L1Premium = [APP_DEL getImage:[dict valueForKey:@"image1L1Premium"]];
                imgView1Layout1Premium.image = [ImageOperations imageByCombiningImages:imgView1Layout1Premium.image withImage:presetImg1L1Premium];
                imgView1Layout1PremiumPSUOn.image = [ImageOperations imageByCombiningImages:imgView1Layout1PremiumPSUOn.image withImage:presetImg1L1Premium];
                presetImg1L1Premium=nil;

                UIImage *presetImg1L2Standard = [APP_DEL getImage:[dict valueForKey:@"image1L2Standard"]];
                imgView1Layout2Standard.image = [ImageOperations imageByCombiningImages:imgView1Layout2Standard.image withImage:presetImg1L2Standard];
                imgView1Layout2StandardPSUOn.image = [ImageOperations imageByCombiningImages:imgView1Layout2StandardPSUOn.image withImage:presetImg1L2Standard];
                presetImg1L2Standard=nil;

                UIImage *presetImg1L2Premium = [APP_DEL getImage:[dict valueForKey:@"image1L2Premium"]];
                imgView1Layout2Premium.image = [ImageOperations imageByCombiningImages:imgView1Layout2Premium.image withImage:presetImg1L2Premium];
                imgView1Layout2PremiumPSUOn.image = [ImageOperations imageByCombiningImages:imgView1Layout2PremiumPSUOn.image withImage:presetImg1L2Premium];
                presetImg1L2Premium=nil;

            }

            @autoreleasepool
            {

                UIImage *presetImg2L1Standard = [APP_DEL getImage:[dict valueForKey:@"image2L1Standard"]];
                imgView2Layout1Standard.image = [ImageOperations imageByCombiningImages:imgView2Layout1Standard.image withImage:presetImg2L1Standard];
                imgView2Layout1StandardPSUOn.image = [ImageOperations imageByCombiningImages:imgView2Layout1StandardPSUOn.image withImage:presetImg2L1Standard];
                presetImg2L1Standard=nil;

                UIImage *presetImg2L1Premium = [APP_DEL getImage:[dict valueForKey:@"image2L1Premium"]];
                imgView2Layout1Premium.image = [ImageOperations imageByCombiningImages:imgView2Layout1Premium.image withImage:presetImg2L1Premium];
                imgView2Layout1PremiumPSUOn.image = [ImageOperations imageByCombiningImages:imgView2Layout1PremiumPSUOn.image withImage:presetImg2L1Premium];
                presetImg2L1Premium=nil;

                UIImage *presetImg2L2Standard = [APP_DEL getImage:[dict valueForKey:@"image2L2Standard"]];
                imgView2Layout2Standard.image = [ImageOperations imageByCombiningImages:imgView2Layout2Standard.image withImage:presetImg2L2Standard];
                imgView2Layout2StandardPSUOn.image = [ImageOperations imageByCombiningImages:imgView2Layout2StandardPSUOn.image withImage:presetImg2L2Standard];
                presetImg2L2Standard=nil;

                UIImage *presetImg2L2Premium = [APP_DEL getImage:[dict valueForKey:@"image2L2Premium"]];
                imgView2Layout2Premium.image = [ImageOperations imageByCombiningImages:imgView2Layout2Premium.image withImage:presetImg2L2Premium];
                imgView2Layout2PremiumPSUOn.image = [ImageOperations imageByCombiningImages:imgView2Layout2PremiumPSUOn.image withImage:presetImg2L2Premium];
                presetImg2L2Premium=nil;


            }

            @autoreleasepool
            {

                UIImage *presetImg3L1Standard = [APP_DEL getImage:[dict valueForKey:@"image3L1Standard"]];
                imgView3Layout1Standard.image = [ImageOperations imageByCombiningImages:imgView3Layout1Standard.image withImage:presetImg3L1Standard];
                imgView3Layout1StandardPSUOn.image = [ImageOperations imageByCombiningImages:imgView3Layout1StandardPSUOn.image withImage:presetImg3L1Standard];
                presetImg3L1Standard=nil;

                UIImage *presetImg3L1Premium = [APP_DEL getImage:[dict valueForKey:@"image3L1Premium"]];
                imgView3Layout1Premium.image = [ImageOperations imageByCombiningImages:imgView3Layout1Premium.image withImage:presetImg3L1Premium];
                imgView3Layout1PremiumPSUOn.image = [ImageOperations imageByCombiningImages:imgView3Layout1PremiumPSUOn.image withImage:presetImg3L1Premium];
                presetImg3L1Premium=nil;

                UIImage *presetImg3L2Standard = [APP_DEL getImage:[dict valueForKey:@"image3L2Standard"]];
                imgView3Layout2Standard.image = [ImageOperations imageByCombiningImages:imgView3Layout2Standard.image withImage:presetImg3L2Standard];
                imgView3Layout2StandardPSUOn.image = [ImageOperations imageByCombiningImages:imgView3Layout2StandardPSUOn.image withImage:presetImg3L2Standard];
                presetImg3L2Standard=nil;


                UIImage *presetImg3L2Premium = [APP_DEL getImage:[dict valueForKey:@"image3L2Premium"]];
                imgView3Layout2Premium.image = [ImageOperations imageByCombiningImages:imgView3Layout2Premium.image withImage:presetImg3L2Premium];
                imgView3Layout2PremiumPSUOn.image = [ImageOperations imageByCombiningImages:imgView3Layout2PremiumPSUOn.image withImage:presetImg3L2Premium];
                presetImg3L2Premium=nil;

            }



        }

        @autoreleasepool
        {
            UIImage *presetImg4Standard = [APP_DEL getImage:[dict valueForKey:@"image4Standard"]];
            mainImageView4.image=[ImageOperations imageByCombiningImages:mainImageView4.image withImage:presetImg4Standard];
            presetImg4Standard=nil;

            UIImage *presetImg5Standard = [APP_DEL getImage:[dict valueForKey:@"image5Standard"]];
            mainImageView5.image=[ImageOperations imageByCombiningImages:mainImageView5.image withImage:presetImg5Standard];
             presetImg5Standard=nil;
        }

        @autoreleasepool
        {
            UIImage *presetImg1Detail1 = [APP_DEL getImage:[dict valueForKey:@"image1DetailView1"]];
            imgView1Detail.image = [ImageOperations imageByCombiningImages:imgView1Detail.image withImage:presetImg1Detail1];
             presetImg1Detail1=nil;

            UIImage *presetImg2Detail2 = [APP_DEL getImage:[dict valueForKey:@"image2DetailView2"]];
            imgView2Detail.image = [ImageOperations imageByCombiningImages:imgView2Detail.image withImage:presetImg2Detail2];
            presetImg2Detail2=nil;

             UIImage *presetImg3Detail3 = [APP_DEL getImage:[dict valueForKey:@"image3DetailView3"]];
            imgView3Detail.image = [ImageOperations imageByCombiningImages:imgView3Detail.image withImage:presetImg3Detail3];
            presetImg3Detail3=nil;

        }


        [self setMaterialReflection];

        NSString *strShowIndicator = [dict valueForKey:@"hideIndicator"];
        if ([strShowIndicator isEqualToString:@"YES"]) {
            [appDelegate hideIndicator];
        }



    }
}

问题在于,当上述方法称为内存使用量超过 400 mb 并且我在控制台中收到 2-3 次收到的内存警告消息并且之后我的应用程序崩溃了。

在运行时,当执行内存中的上述方法总是增加时,即使你在上面的方法中看到我没有所有的 uiimage 对象并且还使用了@autorelese 块但内存没有释放。

我想在每次调用以下方法后减少内存。

  • (UIImage*)imageByCombiningImages:(UIImage*)firstImage withImage:(UIImage*)secondImage

在上述方法中,我将两张图像合并为一张图像,我项目中的所有图像的平均大小为 2 MB。

如果我降低图像分辨率而不是应用程序的质量下降并且这不是我想要的,我已经将图像的大小平均减少到 4 MB 之前的 2 MB。

我已经对此进行了谷歌搜索并试图找到堆栈溢出的解决方案,但仍然一无所获,所以请指导我解决这个问题。

请建议我如何改进图像组合的方法?以及如何优化上述方法?,从而减少内存使用量,我的应用程序可以顺利运行。

【问题讨论】:

  • 您说您有内存泄漏(参见标记),但究竟是哪一行导致了内存泄漏?仪器怎么说?
  • 图片有多大以像素为单位?它们以 2 MB 的大小存储(作为 JPEG、PNG?)并不意味着它们在内存中占用 2 MB...

标签: ios memory-management memory-leaks uiimageview uiimage


【解决方案1】:

我可以建议的一件事是不要每次都从图形上下文中捕获图像。

尝试以下步骤:

  1. 将字典传递给 imageByCombiningImages 方法:
  2. 计算框架
  3. 迭代字典中的所有图像并绘制
  4. 只捕获一次最终图像。

编辑 1:

编辑方法:

    + (UIImage*)imageByCombiningImages:(NSDictionary *)imageDict
    {
     UIImage *image = nil;

            CGSize newImageSize = CGSizeZero;
    NSArray *allImages = [imageDict allValues];
    for (UIImage *image in allImages) {
    newImageSize  = CGSizeMake(MAX(image.size.width, newImageSize.size.width), MAX(image.size.height, newImageSize.size.height));
    }


            if (UIGraphicsBeginImageContextWithOptions != NULL)
            {
                UIGraphicsBeginImageContextWithOptions(newImageSize, NO, [[UIScreen mainScreen] scale]);
            }
            else
            {
                UIGraphicsBeginImageContext(newImageSize);
            }

    for (UIImage *image in allImages) {
            [image drawAtPoint:CGPointMake(roundf((newImageSize.width-image.size.width)/2), roundf((newImageSize.height-image.size.height)/2))];
    }

image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image;

    }

编辑 2:

当您使用 UIGraphicsGetImageFromCurrentImageContext() 创建图像时,核心图形会在内部创建 cg 栅格数据 以创建位图图像。它会消耗更多的内存。

您可以通过延迟顺序方法调用来合并图像来避免内存警告。

编辑 3:

修改后的图片合并方式:

- (void)imageByCombiningImages:(UIImage*)firstImage withImage:(UIImage*)secondImage
{

        UIImage *image = nil;

        CGSize newImageSize = CGSizeMake(MAX(firstImage.size.width, secondImage.size.width), MAX(firstImage.size.height, secondImage.size.height));
        if (UIGraphicsBeginImageContextWithOptions != NULL)
        {
            UIGraphicsBeginImageContextWithOptions(newImageSize, NO, [[UIScreen mainScreen] scale]);
        }
        else
        {
            UIGraphicsBeginImageContext(newImageSize);
        }

        [firstImage drawAtPoint:CGPointMake(roundf((newImageSize.width-firstImage.size.width)/2), roundf((newImageSize.height-firstImage.size.height)/2))];

        firstImage =nil;
        [secondImage drawAtPoint:CGPointMake(roundf((newImageSize.width-secondImage.size.width)/2), roundf((newImageSize.height-secondImage.size.height)/2))];


        secondImage=nil;

        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

[self performSelector:@selector(merge:) withObject:image afterDelay:1]; // modify the delay which will work out for you

}

将所有图像键添加到数组中并调用方法,

- (void)merge:(UIImage *)image {

    switch (rowIndex) {
        case 0: {
            UIImage *presetImg1L1Standard = [APP_DEL getImage:[dict valueForKey:[imagesArray objectAtIndex:rowIndex]]];
            switch (sectionIndex) {
                case 0:
                    [ImageOperations imageByCombiningImages:imgView1Layout1Standard.image withImage:presetImg1L1Standard];
                    sectionIndex++;
                    break;
                  case 1:
                    imgView1Layout1Standard.image = image;
                    [ImageOperations imageByCombiningImages:imgView1Layout1StandardPSUOn.image withImage:presetImg1L1Standard];
                    rowIndex ++;
                    sectionIndex = 0;
                    break;
                default:
                    break;
            }
        }
            break;
        case 1: {
            UIImage *image = [APP_DEL getImage:[dict valueForKey:[imagesArray objectAtIndex:rowIndex]]];
            switch (sectionIndex) {
                case 0:
                    imgView1Layout1StandardPSUOn.image = image;
                    [ImageOperations imageByCombiningImages:imgView1Layout1Premium.image withImage:image];
                    sectionIndex++;
                    break;
                case 1:
                    imgView1Layout1Premium.image = image;
                    [ImageOperations imageByCombiningImages:imgView1Layout1Premium.image withImage:presetImg1L1Premium];
                    rowIndex ++;
                    sectionIndex = 0;
                    break;
                default:
                        break;
                }
            }
                break;
// ................. and so on
            default:
                break;
        }
    }

希望这会给你一个想法。

【讨论】:

  • 感谢您的建议...但我不明白您想说什么...您可以编辑我的 imageByCombiningImages: 方法吗?这样我就可以在我的代码中尝试了
  • @PRJ 如果您需要有关 Edit 2 方法的更多帮助,请随时联系。我会帮你写代码 sn-p...
  • 但这是我最后的解决方案,你能提出一些建议如何改进 applyMaterialWithValue: 方法吗?
【解决方案2】:

我会尝试在 GPU 上进行计算,例如使用GPUImageblend filters 之一。

【讨论】: