UIImageView内存没有被回收(又长又详细)
我有一个包含大约 80 个 UIImageView 的 UIView。当我每次 UIView 加载新图像时创建一组新的 80 个 UIImageView 时,丢弃的 UIView 中的内存不会被回收。 (解决方案是继续回收同一组 UIImageView,但我担心内存没有被回收,因为最终我确实希望从回收的 UIImageView 中回收内存。)
这是我的示例代码:
#import "ImageMemViewController.h"
#import <mach/mach.h>
static NSArray *paths;
static NSMutableArray *images;
static NSMutableArray *imageViews;
static vm_size_t 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) {
NSLog(@"Memory used: %f MB", (float)info.resident_size / (1024 * 1024));
return info.resident_size;
}
else
NSLog(@"Error: %s", mach_error_string(kerr));
return 0;
}
@implementation ImageMemViewController
- (void)viewDidLoad
{
[super viewDidLoad];
paths = [NSArray arrayWithObjects:
@"MJR_20070519_0529.jpg",
@"MJR_20070519_0537.jpg",
@"MJR_20070519_0545.jpg",
@"MJR_20070519_0559.jpg",
nil];
images = [[NSMutableArray alloc] initWithCapacity:paths.count];
for (NSString *p in paths)
[images addObject:[UIImage imageNamed:p]];
imageViews = [[NSMutableArray alloc] initWithCapacity:100];
for (int i = 0; i < 100; i++)
[imageViews addObject:[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]];
}
- (void)showWithNewImages
{
// Question: What can be done here to reclaim memory?
#if 0 // this was tried
for (UIImageView *v in self.view.subviews) {
v.image = nil;
[v removeFromSuperview];
}
#else // as was this -- no difference
NSArray *subviews = self.view.subviews;
for (int i = 0; i < subviews.count; i++) {
UIImageView *v = [subviews objectAtIndex:i];
v.image = nil;
[v removeFromSuperview];
}
#endif
int n = 0;
for (int x = 10; x < 700; x += 100)
for (int y = 10; y < 1000; y += 100) {
UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, 90, 90)];
v.image = [images objectAtIndex:n % paths.count];
n++;
[self.view addSubview:v];
[v release];
}
}
- (void)showWithRecycledImages
{
for (UIImageView *v in self.view.subviews) {
v.image = nil;
[v removeFromSuperview];
}
int n = 0;
for (int x = 10; x < 700; x += 100)
for (int y = 10; y < 1000; y += 100) {
UIImageView *v = [imageViews objectAtIndex:n];
v.frame = CGRectMake(x, y, 90, 90);
v.image = [images objectAtIndex:n++ % paths.count];
[self.view addSubview:v];
}
}
- (void)viewDidAppear:(BOOL)animated
{
vm_size_t start = report_memory();
for (int i = 0; i < 1000; i++) {
[self showWithRecycledImages];
if (i % 250 == 0)
report_memory();
}
NSLog(@"showWithRecycledImages memory gain = %f MB", (float)(report_memory() - start) / (1024 * 1024));
start = report_memory();
for (int i = 0; i < 1000; i++) {
[self showWithNewImages];
if (i % 250 == 0)
report_memory();
}
NSLog(@"showWithNewImages memory gain = %f MB", (float)(report_memory() - start) / (1024 * 1024));
}
@end
两个重要的方法是showWithNewImages 和 showWithRecycledImages。前者每次调用时都会分配新的 UIImageViews,而后者只是回收相同的 UIImageViews 集。
以下是输出:
2010-11-06 10:51:28.556 ImageMem[7285:207] Memory used: 12.496094 MB
2010-11-06 10:51:28.582 ImageMem[7285:207] Memory used: 12.636719 MB
2010-11-06 10:51:45.358 ImageMem[7285:207] Memory used: 12.898438 MB
2010-11-06 10:52:03.409 ImageMem[7285:207] Memory used: 13.218750 MB
2010-11-06 10:52:17.430 ImageMem[7285:207] Memory used: 13.546875 MB
2010-11-06 10:52:28.071 ImageMem[7285:207] Memory used: 13.863281 MB
2010-11-06 10:52:28.084 ImageMem[7285:207] showWithRecycledImages memory gain = 1.367188 MB
2010-11-06 10:52:28.087 ImageMem[7285:207] Memory used: 13.863281 MB
2010-11-06 10:52:28.153 ImageMem[7285:207] Memory used: 13.871094 MB
2010-11-06 10:52:40.717 ImageMem[7285:207] Memory used: 17.746094 MB
2010-11-06 10:52:46.620 ImageMem[7285:207] Memory used: 21.621094 MB
2010-11-06 10:52:49.684 ImageMem[7285:207] Memory used: 25.464844 MB
2010-11-06 10:52:52.772 ImageMem[7285:207] Memory used: 29.347656 MB
2010-11-06 10:52:52.775 ImageMem[7285:207] showWithNewImages memory gain = 15.484375 MB
在继续之前,我应该指出,我已经观看了 WWDC 2010 的精彩会议 104“使用滚动视图设计应用程序”,我并且知道如何以及为何回收 UIImageViews。
然而,会议表明回收的目的是避免为每个可能的图像分配 UIImageView,因为只看到两个,并且需要第三个用于动画。他们没有提到的是,当整个视图被卸载时,这三个 UIImageView 使用的内存是否会消失。
好的,现在的问题是:如果将 UIImageView 子视图从其超级视图中删除不起作用,是否有任何方法可以从 UIImageView 子视图中获取内存?
(这不是 UIImage 本身的缓存问题,因为两个示例都使用同一组 UIImage。)
有人有想法吗?
注意:即使在回收时,我也关心内存增益。这最终会杀死我的应用程序,因为我正在展示数千张图像的幻灯片,这些图像将运行几个小时。
tia 的贡献(见下文)并不是答案。 (顺便说一句,我没有修改数组;我正在修改数组是快照的状态。)为了证明这一点,我将子视图删除循环重写为:
UIImageView *views[200]; // Note: C array; does not affect retain counts.
int j = 0;
for (UIImageView *v in self.view.subviews)
views[j++] = v;
for (int i = 0; i < j; i++)
[views[i] removeFromSuperview];
具有完全相同的结果。 (内存增益量相同。)
I have a UIView that contains about 80 UIImageViews. When I create a new set of 80 UIImageViews each time the UIView is loaded with new images, memory from the discarded UIViews is not being recovered. (The solution is to keep recycling the same set of UIImageViews, but my concern is that the memory is not being recovered, because eventually I do want the memory even from the recycled UIImageViews back.)
Here's my example code:
#import "ImageMemViewController.h"
#import <mach/mach.h>
static NSArray *paths;
static NSMutableArray *images;
static NSMutableArray *imageViews;
static vm_size_t 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) {
NSLog(@"Memory used: %f MB", (float)info.resident_size / (1024 * 1024));
return info.resident_size;
}
else
NSLog(@"Error: %s", mach_error_string(kerr));
return 0;
}
@implementation ImageMemViewController
- (void)viewDidLoad
{
[super viewDidLoad];
paths = [NSArray arrayWithObjects:
@"MJR_20070519_0529.jpg",
@"MJR_20070519_0537.jpg",
@"MJR_20070519_0545.jpg",
@"MJR_20070519_0559.jpg",
nil];
images = [[NSMutableArray alloc] initWithCapacity:paths.count];
for (NSString *p in paths)
[images addObject:[UIImage imageNamed:p]];
imageViews = [[NSMutableArray alloc] initWithCapacity:100];
for (int i = 0; i < 100; i++)
[imageViews addObject:[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]];
}
- (void)showWithNewImages
{
// Question: What can be done here to reclaim memory?
#if 0 // this was tried
for (UIImageView *v in self.view.subviews) {
v.image = nil;
[v removeFromSuperview];
}
#else // as was this -- no difference
NSArray *subviews = self.view.subviews;
for (int i = 0; i < subviews.count; i++) {
UIImageView *v = [subviews objectAtIndex:i];
v.image = nil;
[v removeFromSuperview];
}
#endif
int n = 0;
for (int x = 10; x < 700; x += 100)
for (int y = 10; y < 1000; y += 100) {
UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, 90, 90)];
v.image = [images objectAtIndex:n % paths.count];
n++;
[self.view addSubview:v];
[v release];
}
}
- (void)showWithRecycledImages
{
for (UIImageView *v in self.view.subviews) {
v.image = nil;
[v removeFromSuperview];
}
int n = 0;
for (int x = 10; x < 700; x += 100)
for (int y = 10; y < 1000; y += 100) {
UIImageView *v = [imageViews objectAtIndex:n];
v.frame = CGRectMake(x, y, 90, 90);
v.image = [images objectAtIndex:n++ % paths.count];
[self.view addSubview:v];
}
}
- (void)viewDidAppear:(BOOL)animated
{
vm_size_t start = report_memory();
for (int i = 0; i < 1000; i++) {
[self showWithRecycledImages];
if (i % 250 == 0)
report_memory();
}
NSLog(@"showWithRecycledImages memory gain = %f MB", (float)(report_memory() - start) / (1024 * 1024));
start = report_memory();
for (int i = 0; i < 1000; i++) {
[self showWithNewImages];
if (i % 250 == 0)
report_memory();
}
NSLog(@"showWithNewImages memory gain = %f MB", (float)(report_memory() - start) / (1024 * 1024));
}
@end
The two important methods are showWithNewImages and showWithRecycledImages. The former allocates new UIImageViews each time it's called, and the latter just recycles the same set if UIImageViews.
Here's the output:
2010-11-06 10:51:28.556 ImageMem[7285:207] Memory used: 12.496094 MB
2010-11-06 10:51:28.582 ImageMem[7285:207] Memory used: 12.636719 MB
2010-11-06 10:51:45.358 ImageMem[7285:207] Memory used: 12.898438 MB
2010-11-06 10:52:03.409 ImageMem[7285:207] Memory used: 13.218750 MB
2010-11-06 10:52:17.430 ImageMem[7285:207] Memory used: 13.546875 MB
2010-11-06 10:52:28.071 ImageMem[7285:207] Memory used: 13.863281 MB
2010-11-06 10:52:28.084 ImageMem[7285:207] showWithRecycledImages memory gain = 1.367188 MB
2010-11-06 10:52:28.087 ImageMem[7285:207] Memory used: 13.863281 MB
2010-11-06 10:52:28.153 ImageMem[7285:207] Memory used: 13.871094 MB
2010-11-06 10:52:40.717 ImageMem[7285:207] Memory used: 17.746094 MB
2010-11-06 10:52:46.620 ImageMem[7285:207] Memory used: 21.621094 MB
2010-11-06 10:52:49.684 ImageMem[7285:207] Memory used: 25.464844 MB
2010-11-06 10:52:52.772 ImageMem[7285:207] Memory used: 29.347656 MB
2010-11-06 10:52:52.775 ImageMem[7285:207] showWithNewImages memory gain = 15.484375 MB
Before continuing, I should note that I have watched the excellent Session 104, "Designing Apps with Scroll Views" from WWDC 2010, I and know all about how and why to recycle UIImageViews.
However, the session suggests that the purpose of recycling is to avoid allocating a UIImageView for every possible image, since only two are ever seen and a third one is needed for animation. What they don't mention is whether the memory used by those three UIImageViews even goes away when the entire view is unloaded.
OK, now for the question: Is there any way to get the memory back from the UIImageView subviews if removing them from their superview doesn't do it?
(This is not an issue of caching of the UIImages themselves, as both examples use the same set of UIImages.)
Ideas anyone?
Note: I am also concerned with the memory gain even when recycling. This will eventually kill my app, since I am showing a slide show of thousands of images that will run for hours.
tia's contribution (see below) is not the answer. (As a side note, I was not modifying the array; I was modifying the state of which the array is a snapshot.) To prove this, I rewrote the subview-removal loop to be this:
UIImageView *views[200]; // Note: C array; does not affect retain counts.
int j = 0;
for (UIImageView *v in self.view.subviews)
views[j++] = v;
for (int i = 0; i < j; i++)
[views[i] removeFromSuperview];
with exactly the same results. (Memory gain amount is identical.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我在这里怀疑两点:
[self.view.subviews copy]
。removeFromSuperview
可能会执行autorelease
而不是release
,因此在这种情况下内存不会立即回收。编辑:
这里有一个内存泄漏:
也许您忘记释放图像视图。
还有一点是,如果您想恢复内存,您还需要释放或删除
imageViews
中的所有项目。I would doubt for two points here:
[self.view.subviews copy]
.removeFromSuperview
might doautorelease
rather thanrelease
, so the memory will not be reclaimed immediately in that case.edited:
There is one memory leak here:
maybe you forgot to release the image view.
One more point is, you need to release or remove all items from your
imageViews
as well if you want to recover the memory.