应用程序为CG光栅数据加载分配了650MB的RAM,大约有250个UI图像。

3
我目前正在开发一款iPad应用程序,该应用程序将大约250个UIImage加载到UIButton中(然后更改颜色),创建世界地图 - 每个国家都有自己的按钮和相应的图像 - 在加载游戏时。我遇到的问题是,在retina iPad上,应用程序在加载图像时使用了约650MB的RAM,这是不可思议的。
当应用程序最初加载游戏时,它使用以下代码将图像设置为按钮(Territory是UIButton的子类)。
//Initialize the arrays of each territory and add an action to the territory
    for (int i = (int)territoryArray.count - 1; i >= 0; i--) {

        @autoreleasepool {

            //Cast the object into a territory object
            Territory *ter = (Territory *)[territoryArray objectAtIndex:i];

            //Set the territory's image
            [ter setImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@T%i.png", [defaults objectForKey:@"Current_Map"], i + 1] ofType:nil]] forState:UIControlStateNormal];
        }
    }

这导致以下的内存使用情况。尽管块被@autoreleasepool包围,但循环运行时会出现峰值,且该峰值不会消失。请注意,ImageIO正在使用内存:

Without coloring

当领土的图像文件中包含原始颜色时,该屏幕截图被拍摄。我正在使用MGImageUtilities中的UIImage+Tint库对图像进行着色。在使用该库时,使用以下代码:

[ter setImage:[[ter imageForState:UIControlStateNormal] imageTintedWithColor:[UIColor colorWithRed:[[colors objectAtIndex:0] floatValue]/255.0 green:[[colors objectAtIndex:1] floatValue]/255.0 blue:[[colors objectAtIndex:2] floatValue]/255.0 alpha:1.0]] forState:UIControlStateNormal];

在另一个循环中使用此代码(该循环由另一个函数中的@autoreleasepool包围),在加载所有图像后,会出现以下内存使用情况。请注意,CG光栅数据正在使用内存。

With coloring

我不知道为什么使用内存的事物会有所不同。
我在苹果开发者论坛上发布了一个帖子,预示了这个问题。
我也联系过苹果开发者TSI,他们建议使用“惰性图像加载”。我研究了一下,但我没有找到在非基于页面的UIScrollView上执行此操作的方法。
现在我已经迷失了如何解决这个问题。我尝试了我能想到的一切,花了几个小时来尝试解决这个问题,但似乎找不到任何可行的解决方案。如果有人能帮我,我真的很感激。如果您想查看更多信息或代码,请告诉我,我很乐意发布它。提前感谢您的帮助。
编辑:
我一直在尝试,我正在使用以下代码在scrollViewDidScroll:中工作。它似乎起作用了,但是内存并没有被释放,它继续增加。有什么想法吗?
CGRect currentF = [scrollView convertRect:scrollView.bounds toView:scrollView.contentView];

    //Loop through all territories and determine which need to be rendered
    for (int i = (int)territoryArray.count - 1; i >= 0; i--) {

        @autoreleasepool {

            //See if the territory is on-screen
            if (CGRectIntersectsRect(currentF, [[territoryArray objectAtIndex:i] frame])) {

                //See if the territory needs an image
                if ([[territoryArray objectAtIndex:i] image] == nil) {

                    //Set the territory's image
                    [[territoryArray objectAtIndex:i] setImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@T%i.png", [defaults objectForKey:@"Current_Map"], i + 1] ofType:nil]] forState:UIControlStateNormal];
                }
            } else {
                //Remove the territory's image, it is off-screen
                [[territoryArray objectAtIndex:i] setImage:nil forState:UIControlStateNormal];
            }
        }
    }

公平地说,你不能期望一次性加载250张图片而不会出现这样的内存膨胀。你需要一种增量式瓦片加载方式。 - CodaFi
2个回答

1

首先,感谢用户3339357提供的所有帮助。你为我的最终解决方案奠定了基础。

解决方案:

我调整了scrollViewDidScroll:函数,以创建缺失的图像并释放不再需要的图像:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {

    //Get the frame that the scroll view is currently showing
    CGRect visibleF = [scrollView convertRect:scrollView.bounds toView:scrollView.contentView];

    //Loop through all territories and determine which need to be rendered
    for (int i = (int)territoryArray.count - 1; i >= 0; i--) {

        //Put the following in an autoreleasepool to conserve memory
        @autoreleasepool {

            //See if the territory is on-screen
            if (CGRectIntersectsRect(visibleF, [[territoryArray objectAtIndex:i] frame])) {

                //See if the territory needs an image
                if ([[territoryArray objectAtIndex:i] image] == nil) {

                    //Set the territory's image
                    [[territoryArray objectAtIndex:i] setImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@T%i.png", [defaults objectForKey:@"Current_Map"], i + 1] ofType:nil]]];

                    //Update the territory's color
                    [self updateColorOnTerritory:[territoryArray objectAtIndex:i]];
                }
            } else {

                //Remove the territory's image, it is off-screen
                [[territoryArray objectAtIndex:i] setImage:nil];
            }
        }
    }
}

基本上,该代码获取滚动视图内容视图的可见帧(创建visibleF代码行也允许在滚动视图上缩放/缩放)。程序然后循环遍历一个数组,该数组保存了滚动视图中每个Territory对象的引用,并确定哪些领土应该是可见的,并设置它们的图像(如果不存在)。它还重置了当前屏幕上不可见的图像视图。
值得注意的是,我调整了OBShapedButton库,并将其更改为UIImageView的子类,因为我无法强制按钮释放它们不再使用的内存。我使用了UITapGestureRecognizer代替IBAction
编辑:
上面的代码会导致滚动视图中的延迟(非常多)。我通过在我为UIScrollView创建的MapScrollView子类中添加以下代码来解决这个问题:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
    return NO;
}

希望这能帮助其他遇到类似问题的人!

1
我发现当我需要将图片加载到滚动视图中时,为了避免内存问题和提高应用程序的响应能力,你需要逐步加载这些图片。也就是说,虽然你可能在滚动视图中有250张图片,但你不会同时看到它们所有。因此,只需加载可见的瓷砖和其下面几行即可。当你滚动时可以加载剩余的图片。

编辑:这里有一个例子。

-(void)scrollViewDidScroll:(UIScrollView *)myScrollView {

int currentPage = (1 + myScrollView.contentOffset.x / kXItemSpacingIphone);
for (ItemView* itemView in [self.itemRow subviews]){
    if (itemView.tag >= currentPage-1 && itemView.tag <= currentPage+1)
    {
        //keep it visible
        if (!itemView.isLoaded) {
            [itemView layoutWithData:[self.items objectAtIndex:itemView.tag-1]];
        }
    }
    else
    {
        //hide it
        if (itemView.isLoaded) {
            [itemView unloadData];
        }

    }
}

}

上面的代码将加载可见页面上方和下方的图像。我发现页面滚动有点卡顿。但是我正在从网络加载图像,所以您可能会发现这个功能很好。

祝你好运!

编辑2:

如果您的滚动视图没有设置为分页模式,请尝试以下操作。

  1. 将我的视图控制器设置为滚动视图的代理(如果您在代码中执行此操作,则必须修改视图控制器的.h文件,以声明其符合UIScrollViewDelegate)。

  2. 定义一个scrollViewDidScroll方法,该方法(a)确定滚动视图可见部分的框架;(b)确定哪些子视图与该可见部分相交;(c)加载可见的项目,并卸载不可见的项目。

最终看起来会像这样。

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    // Determine the frame of the visible portion of the scrollview.

    CGRect visibleScrollViewFrame = scrollView.bounds;
    visibleScrollViewFrame.origin = scrollView.contentOffset;

    // Now iterate through the various items, remove the ones that are not visible,
    // and show the ones that are.

    for (Item *itemObject in self.itemCollection)
    {
        // Determine the frame within the scrollview that the object does (or 
        // should) occupy.

        CGRect itemObjectFrame = [self getItemObjectFrame:itemObject];

        // see if those two frames intersect

        if (CGRectIntersectsRect(visibleScrollViewFrame, itemObjectFrame))
        {
            // If it's visible, then load it (if it's not already).
            // Personally, I have my object have a boolean property that
            // tells me whether it's loaded or not. You can do this any
            // way you want.

            if (!itemObject.loaded)
                [itemObject loadItem];
        }
        else
        {
            // If not, go ahead and unload it (if it's loaded) to conserve memory.

            if (itemObject.loaded)
                [itemObject unloadItem];
        }
    }
}

参考文献


这正是我正在考虑的...你所描述的是“懒加载图片”,对吗?如果是这样,你可以发一下使用UIScrollView实现这个功能的示例吗?我看到了类似于这样的内容,但是我还没有完全掌握如何实现:http://stackoverflow.com/questions/11750164/ios-uiscrollview-lazy-loading - rebello95
你应该下载一些WWDC视频并观看。 - gnasher729
我已经添加了一个例子,请看一下并告诉我! - user3339357
感谢您添加示例!我理解这个概念,但视图上的对象没有统一布局(由于图像的形状方式,很多对象重叠)。您能解释一下kXItemSpacingIphone[self.itemRow subviews]的用法吗?非常感谢,对于所有问题我很抱歉。编辑:滚动视图目前未设置支持分页,我也需要更改吗? - rebello95
我添加了另一个示例,您可能不需要使用分页。我没有意识到您的图像重叠等问题。我期望在滚动视图上看到简单的平铺图像视图。也许新的示例会更有帮助。 - user3339357
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接