【问题标题】:App Allocating 650MB of RAM to CG Raster Data Loading ~250 UIImages应用程序分配 650MB 的 RAM 到 CG 光栅数据加载 ~250 UIImages
【发布时间】:2014-04-09 03:06:31
【问题描述】:

我目前正在开发一个 iPad 应用程序,它将 ~250 UIImages 加载到 UIButtons(然后更改颜色),创建世界地图 - 每个国家都有自己的按钮和相应的图像 - 当加载游戏。我遇到的问题是,在视网膜 iPad 上,应用程序在加载图像时使用了大约 650MB 的 RAM,这太疯狂了。

当应用最初加载游戏时,它使用以下代码将图像设置为按钮(TerritoryUIButton 的子类)。

//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 正在使用内存:

该屏幕截图是在领土的图像文件中具有原始颜色时拍摄的。我正在使用来自MGImageUtilitiesUIImage+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 raster data 正在使用内存。

不知道为什么使用内存的东西不一样。

我在 Apple 开发者论坛上 posted a thread 上也发布了这个问题。

我还联系了 Apple Developer 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 张图像而没有像这样的内存气球。您需要某种增量图块加载。

标签: ios objective-c xcode uiimage


【解决方案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];
        }
    }
}

Reference

【讨论】:

  • 这就是我的想法...您所描述的是“延迟图像加载”,对吗?如果是这样,您能否发布一个如何使用UIScrollView 执行此操作的示例?我在看这样的东西,但我不太了解如何实现它:stackoverflow.com/questions/11750164/…
  • 您应该下载一些 WWDC 视频并观看它们。
  • 我加了一个例子,看看告诉我!
  • 感谢您添加示例!我理解这个概念,但是视图上的所有对象都没有统一布局(由于图像的形状不同,它们中的很多重叠)。你能解释一下kXItemSpacingIphone[self.itemRow subviews] 的用法吗?非常感谢,对所有问题感到抱歉。编辑:滚动视图当前未设置为支持分页,我也应该更改它吗?
  • 我添加了另一个示例,您可能不必使用分页。我没有意识到您的图像重叠等。我希望滚动视图上有一个简单的图像平铺视图。也许这个新的例子会帮助你更多。
【解决方案2】:

首先,感谢 user3339357 的所有帮助。您为我的最终解决方案奠定了基础。

解决方案:

我对@9​​87654321@函数进行了如下调整,以创建丢失的图像并释放不再需要的图像:

-(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 的子类,因为我在强制按钮释放不再使用的内存时遇到问题。我用UITapGestureRecognizers 代替了IBActions。

编辑:

上面的代码导致滚动视图出现延迟(很多)。我通过在为UIScrollView 创建的MapScrollView 子类中添加以下代码解决了这个问题:

- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
    return NO;
}

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

【讨论】:

    猜你喜欢
    • 2012-09-12
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 2022-07-05
    • 2018-06-17
    • 1970-01-01
    • 1970-01-01
    • 2014-03-20
    相关资源
    最近更新 更多