新款iPad:低内存警告不出现?

21
我正在开发一款在 iPad 上使用的应用程序,这是一个非常图形密集型的应用。我已经能够从 iPad 2 中挤出相当多的性能了,但是针对新版 iPad 的 @2x 图形在内存方面占据了很大的空间。通过使用 Instruments 中的 Activity Monitor,我能够看到我的应用程序的大小已经超过了 300MB-400MB 范围,但我没有收到任何低内存通知。我使用 UINavigationController 来管理我的视图,所以一直向下导航的话会对内存产生累积效应,最终导致我的应用程序被终止。在 iPad 2 上我没有遇到这个问题,因为我正常地接收到低内存警告。我的应用程序已经编码尽可能地清理了内存,并且在该设备上表现良好。
我已经阅读了一些类似的问题: IOS app killed for Low Memory but no Memory Warning received
iPhone app uses 150 MB memory and still no low memory warning! 然而,这些建议似乎都不能解决我的问题。
我已经插入了代码来强制发送低内存通知:
[[UIApplication sharedApplication] _performMemoryWarning];

这确实会使不活跃的视图按预期卸载,并将内存消耗恢复正常。这使用了私有API,是一种黑客行为,因此从实际角度考虑,这不是一个解决方案。我该如何让我的设备正确响应低内存条件,并让我的应用程序知道它需要清理?


你也在iPad 2上测试了iOS 5.1吗? - user180326
你能确认一下,在两个iPad上都使用相同的iOS 5.1版本,当你加载完全相同的图像序列(iPad2使用@1x,iPad3使用@2x)和视图控制器时,应用程序在iPad3上终止而在iPad2上没有终止吗?另一个测试是,如果你去掉@2x图像,当iPad3加载更多图像后,会收到内存警告还是应用程序仍然会终止? - viggio24
我可以确认,在iPad 2(运行5.1)上,完全相同的事件序列将正常工作,但在iPad 3上会导致崩溃。我必须对iPad 2进行相当严格的操作,才能使其陷入低内存状态。 - Wayne Hartman
当我删除所有的@2x图像时,我的应用程序的占用空间与iPad 2相同,并且表现良好。 - Wayne Hartman
6个回答

12
我联系了苹果客服解决我的内存问题,并询问了 iPad 3 上的低内存警告:

- 由于内存警告是在主线程上发送的,如果您的应用程序阻塞主线程,则不会接收到内存警告。

- 即使您的应用程序没有阻塞主线程,内存使用量也可能快速增长到足以导致在释放内存之前无法传递内存警告而终止您的应用程序。

- 当内核在各种内存压力级别之间转换时,内存警告触发。因此,常见情况是应用程序接收到内存警告,然后在内存耗尽之前被杀死相当长的时间。初始内存警告释放了足够的内存以保持应用程序处于活动状态,但不足以让内核过渡到较低的内存压力级别。

基于所有这些原因,应将内存警告视为硬件状态的有用数据和指导应用程序在给定设备上使用多少内存的良好指南,但不应依赖它作为防止应用程序被终止的工具。

也许这可以帮助你......


我根本没有收到任何内存警告。我已将主线程阻塞降至最低限度。我认为问题在于我的应用程序使用的内存量与内存条件发生时加载我的重型xib之间存在相当狭窄的差距,超出了该差距并且未经警告终止。 - Wayne Hartman
我创建了一个测试应用程序,通过运行while循环并分配NSData对象来破坏内存。在iPad 2上,我收到了警告,但是在完全相同的应用程序中,在iPad 3上没有警告。 - robbash

2

这个问题在iOS 5.1.1中已经解决。对于那些使用5.1的用户,我实现了自己的内存监视器,并发送与发出真正的内存警告时类似的通知。

我首先在UIApplication上创建了一个分类。这将发布一个UIImage(或其后备缓存)侦听以卸载缓存图像的通知。

.h

@interface UIApplication (ForceLowMemory)

+ (void) forceUnload;

@end

.m

#import "UIApplication+ForceLowMemory.h"

@implementation UIApplication (ForceLowMemory)

+ (void)forceUnload {
    [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification 
                                                        object:[UIApplication sharedApplication]];
}

@end

接下来,我创建了一个内存管理器的看门狗,如下所示: .h文件
@interface ELMemoryManager : NSObject

- (void)startObserving;
- (void)stopObserving;

+ (ELMemoryManager*) sharedInstance;

@end

.m

#import "ELMemoryManager.h"
#import "UIApplication+ForceLowMemory.h"
#import <mach/mach.h>

@interface ELMemoryManager()

@property (nonatomic, retain) NSTimer *timer;
uint report_memory(void);

@end

#define MAX_MEM_SIZE 475000000

@implementation ELMemoryManager
@synthesize timer = timer_;

static ELMemoryManager* manager;

#pragma mark - Singleton

+ (void) initialize {
    if (manager == nil) {
        manager = [[ELMemoryManager alloc] init];
    }
}

+ (ELMemoryManager*) sharedInstance {
    return manager;
}

#pragma mark - Instance Methods

uint report_memory(void) {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(),
                                   TASK_BASIC_INFO,
                                   (task_info_t)&info,
                                   &size);
    if( kerr == KERN_SUCCESS ) {
        return info.resident_size;
    } else {
        return 0;
    }
}

- (void)startObserving {
    if (!self.timer) {
        NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:@selector(checkMemory:) userInfo:nil repeats:YES];
        self.timer = timer;
    }
    [self.timer fire];
}

- (void)stopObserving {
    [self.timer invalidate];
    self.timer = nil;
}

- (void)checkMemory:(id)sender {
    uint size = report_memory();
    if (size > MAX_MEM_SIZE) {
        NSLog(@"we've busted the upper limit!!!");
        [UIApplication forceUnload];
    }
}

#pragma mark - Memory Management
- (void)dealloc {
    [self.timer invalidate];
    [timer_ release];
    [super dealloc];
}

@end

如果我在AppDelegate的receiveMemoryWarning:(或者其他签名)中设置断点,如果我发送你使用的通知,它将不会被调用。你也是这样吗? - Krumelur

1
以下是我的经验。我很高兴被更正...
你如何加载你的图片?
如果你正在使用:
[UIImage imageNamed:(NSString *)]

那么你需要确保有一个好的理由。如果您正在大量使用需要被缓存的图像,则它是一个很好的选择。否则我建议您使用

[UIIMage imageWithContentsOfFile:(NSString *)]

iOS似乎存在一些问题,即使没有任何引用,通过imageNamed加载的图像也无法释放。由于您的应用程序将不再引用该图像,因此您可能不会收到内存警告。但这并不意味着内存已被释放。iOS倾向于在内存中保留这些图像比您想要的时间长得多。当它通常会发出内存警告时,它将终止应用程序。

我还强烈建议打开自动引用计数(ARC)。

Edit -> Refactor -> Convert to Objective-C ARC...

我曾经遇到过类似的问题。通过对我的应用进行上述更改,停止了崩溃,并防止了因为反复使用imageNamed重新加载相同图像而导致的内存泄漏。

希望这能有所帮助。


在iOS 2.x中,**[UIImage imageNamed:]** 方法存在泄漏问题,但在iOS 3.0中已经修复。 - Martin Pilch
图片通过IB加载。 - Wayne Hartman
可能这就是问题所在。 - Martin Pilch
@MartinPilch - 我遇到的问题与 [UIImage imageNamed:] 函数有关,它发生在5.0和5.1版本中。当我反复加载和删除大型(全屏)图像(在走马灯中)时,就会出现这个问题。我只会加载当前、前一个和后一个图像。一旦它们离开屏幕,我就会卸载它们(将它们替换为占位符图像)。当它们返回到任何三个位置时,我会再次加载它们。使用 [UIImage imageNamed:] 多次执行此操作会导致可用内存减少到零,并且应用程序崩溃(没有内存警告)。 - BuzzCloudAU
@user499177 我遇到了同样的问题。这是因为自动释放对象基本上是泄漏的。我建议你使用 NSAutoreleasePool,并在完成对图像的操作后进行排空。 - Martin Pilch

1

听起来问题可能是你的图片没有被释放。根据UIImage文档(以及我的经验),imageNamed:会缓存它加载的图像。因此,你应该将其用于小图标或几乎经常使用的图像,但通常不建议将其用于大型图像或很少使用的图像。正如user499177所说,你应该使用imageWithContentsOfFile:(你可以使用[NSBundle mainBundle]方法来获取路径):

此方法在系统缓存中查找具有指定名称的图像对象,并返回该对象(如果存在)。如果匹配的图像对象尚未在缓存中,则此方法从指定文件加载图像数据,将其缓存,然后返回结果对象。


99.99%的图片都是通过XIB加载的。 - Wayne Hartman
我非常确定XIB加载使用缓存。如果您有许多视图,每个视图都有不同的背景(例如),那么这将很快消耗您可用的RAM。 - Benjie

0

非常感谢。这让我很放心。我也在做一个图像密集型应用程序(在动画中有很多UIImage对象和UIImageView),并且在我的应用程序委托中有通常的内存警告代码,但从未触发过。尽管如此,Instruments在我加载图像、在它们上面绘制并将它们提交给图片视图时显示了问题。我正在使用ARC,从CGImageRef等方面消除了泄漏,但是如果您在ImageView中快速加载足够多的图像,最终还是会崩溃,而日志、应用程序委托方法回调或工具则没有任何警告。应用程序突然被拔掉了地毯而没有“请允许”的话。

还没有机会在iPad2上尝试这个,但无论如何,至少需要有一些指示,至少是极简主义者的控制台消息之类的东西。大多数加载都发生在我自己的GCD队列上,而不是主线程,尽管根据定义,对屏幕控件的更新必须在主线程上完成。所以我想,如果在运行即将停止时阻塞了主线程,那么我猜你只能得到一个匿名的失败。当然,为了得到某种控制台消息将会很有帮助。


我不得不创建自己的内存管理器并手动发布通知,以导致非活动视图卸载和UIImage刷新图像缓存。即使像你一样在后台线程上尝试一次性加载太多内容时,我仍然会偶尔遇到崩溃。可能存在一些苹果看门狗进程,防止突发内存使用量增加,即使您尚未达到最大内存阈值。 - Wayne Hartman

0

我一直在开发一个消耗大量内存的应用程序,我的建议是:

  1. 使用alloc - init和release,避免使用autorelease对象。
  2. 检查内存泄漏
  3. 如果需要使用autorelease对象,请在创建对象之前使用NSAutoreleasePool,并在完成对象工作后释放池。
  4. 如果您正在使用OpenGL,请记得删除所有创建的对象,特别是纹理。
  5. 也许您应该尝试切换到ARC。

我主要使用alloc/init,没有任何泄漏,并且在必要时使用自动释放池。 - Wayne Hartman
只是顺便说一下,我正在使用ARC处理一个项目,该项目在各个地方都使用了大型2X位图,但我还没有在第三代iPad上看到低内存错误(或崩溃)。在经历了一个艰难的开始后,我现在对ARC印象非常好。 - Rob Reuss

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