【问题标题】:iOS [ARC] app not releasing memoryiOS [ARC] 应用程序未释放内存
【发布时间】:2013-08-27 02:00:46
【问题描述】:

我的应用程序,在我编写的一个过程中,在内存中上升,似乎没有释放它。

首先我想提的是,我所写内容的基本大纲是这样的:
- 请求一个 url(使用 NSData -initWithContentsOfURL 获取数据)
- 使用 NSJSONSerialization +JSONObjectWithStream
将 NSData 解析为 NSDictionarys 的 NSArray - 使用 FMDB 框架循环通过解码的 NSArray 在 sqlite 数据库中插入/更新/删除记录并解码数据

应用程序执行上述操作,但它在一个不确定的时间段内循环执行,其中应用程序显示“正在加载”HUD。我认为这可能值得一提,尽管我发现它执行此过程的次数无关紧要,因为如果它正确释放,它不应该影响内存使用。如果我在这里错了,请告诉我。

我的代码运行良好,它完成了预期的工作。但是,当我分析应用程序代码时,内存似乎一直在增加。它确实在整个过程中有所下降,但总体而言它一直在上升(IE 并未完全释放它之前使用的内容)。

如前所述,我已经使用分配、泄漏、VM 跟踪器对应用程序进行了概要分析,并使用了跟踪亮点。

Trace Highlights:显示内存使用率逐渐上升,但会减少一些内存(不是全部),这意味着如果进程运行足够长的时间,内存将达到高使用率并终止。

分配:看起来不错。分配有峰值,但总是回到它开始的地方。我拍了很多照片,它们总是下降,每段最多留下 500-700kb(留下大约 10 分钟)

VM Tracker:证明内存一直在增加,并且没有释放全部内存(如跟踪突出显示的那样)。居民似乎真的很高

泄漏:在应用程序中未发现泄漏

这是 Allocations/VM Tracker 运行的一些屏幕截图:

值得注意的是,我实际上已经尝试过:
- 添加自动释放池
- 通过分配每个属性来“强制释放”;例如 NSURLs、NSRequests 等;为零

我的问题:
- 我应该做一些特别的事情来释放内存吗?
- 我该如何进一步调试这个问题?
- 我怎样才能从 Instruments 提供的数据中找出问题所在?

---- 编辑: ----
这是发送 url 请求以获取数据的代码。:

- (void) requestAndParse : (NSString *)url 
{
    NSURL *theURL;
    ASIHTTPRequest *request;
    NSData *collectedData;
    NSError *error;
    @try {
                    // File cache the NSData
                    theURL = [[NSURL alloc] initWithString: url];
                    request = [ASIHTTPRequest requestWithURL: theURL];
                    [request setDownloadDestinationPath: [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingString:@"/cachefile.txt"]];
                    [request startSynchronous];
                    [request waitUntilFinished];

                    collectedData = [[NSData alloc] initWithContentsOfFile:[[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingString:@"/cachefile.txt"]];


                    if ([collectedData length] > 0) {
                        records = [NSJSONSerialization JSONObjectWithData:collectedData options:NSJSONReadingMutableContainers error:&error];
                    }

    }
    @catch (NSException *exception) {

                    // Failed
                    NSLog(@"Parse error: %@", error);

    }
    @finally {

                    // DB updates with the records here
                    ...

                    // remove file
                    [[NSFileManager defaultManager] removeItemAtPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingString:@"/cachefile.txt"] error:nil];
                    // release properties used
                    collectedData = nil;
                    request = nil;
                    theURL = nil;
    }

}

上面的方法是在应用程序委托的一个while循环中调用的。如前所述,while 循环的长度未定。

--- 编辑 2:---

以下是@finally 语句中发生的情况(使用 FMDB 更新 SQLite 数据库)。我的课上有很多这样的方法,每个表一个。不过,它们都遵循相同的模式,因为它们都是从第一个复制而来的:

-(BOOL) insertBatchOfRecords:(NSArray *)records {

__block BOOL queueReturned = YES;

@autoreleasepool {

    FMDatabaseQueue *dbQueue = [self instantiateDatabaseQueue];
    [dbQueue inTransaction:^(FMDatabase *tdb, BOOL *rollback) {
        if (![tdb open]) {
            NSLog(@"Couldn't open DB inside Transaction");
            queueReturned = NO;
            *rollback = YES;
            return;
        }

        for (NSDictionary *record in records) {
            [tdb executeUpdate:@"INSERT OR REPLACE INTO table (attr1, attr2) VALUES (?,?)", [record valueForKey:@"attr1"], [record valueForKey:@"attr2"]];

            if ([tdb hadError]) {
                queueReturned = NO;
                *rollback = YES;
                NSLog(@"Failed to insert records because %@", [tdb lastErrorMessage]);
                return;
            }
        }
    }];

    [dbQueue close];
    dbQueue = nil;

}

return queueReturned;
}

接下来是-instantiateDatabaseQueue方法:

-(FMDatabaseQueue *) instantiateDatabaseQueue {
@autoreleasepool {
    return [FMDatabaseQueue databaseQueueWithPath: [self.getDocumentsDirectory stringByAppendingPathComponent:@"localdb.db"]];
}
}

autoreleasepools 可能会造成混乱,但代码最初没有这些。我在不同的地方实施了它们,看看是否有任何改进(没有)。

--- 编辑 3 ---

过去几天我一直在分析应用程序,但仍然没有找到答案。我已将有问题的应用程序部分分离到它自己的单独项目中,以确保它确实是导致内存使用的原因。事实证明这是正确的,因为该应用程序的行为仍然相同。

我已经拍摄了进一步的分析照片,但仍然很难确定到底哪里出了问题。看下面,分配看起来不错(VM 对我来说也不算太糟糕?),仍然没有泄漏(没有图片,因为没有!)

但是,当我在 Trace Highlights 上进行分析时,内存使用量一直在上升,直到达到过多使用量(在 3GS 上约为 70+MB),然后由于使用太多内存而崩溃。

我通过使用 ASIHTTPRequest 来获取 NSData(改为存储到文件)来减少问题。请参阅上面的修改后的代码。但是,问题仍然存在,只是需要更长的时间才能发生!

按照最初,问题
- 这个应用程序的第二部分有什么问题吗?

【问题讨论】:

  • +1 不错的第一个问题。不过可能需要包含一些代码。
  • 您是否使用堆栈快照来查看新分配的实际情况?你给runloop一个运行的机会吗? (考虑将长循环分解为更小的操作并使用 NSOperationQueue 处理它们)。
  • 我已经包含了发送 URL 请求并将其分解为数组的实际代码。
  • 我将检查 NSOperationQueue 并将使用堆栈快照
  • 问题仍然存在,但分配结果看起来非常好,只有在 Trace Highlights 上它仍然显示“Real Memory Usage”的攀升,并且当这个值太高时会崩溃。请参阅修改后的帖子

标签: ios cocoa-touch memory


【解决方案1】:

在带有 ARC 的 iOS 中使用 try / catch 会导致内存泄漏,最好避免。

另一种方法是使用异步 NSURLConnection,或使用同步 NSURLConnection 的 NSOperation。

【讨论】:

  • 非常正确。这很可悲,但却是真实的。
  • 在这种情况下,使用的 try/catch 不会导致泄漏,我确信这是在我注意到这个问题后实施的 try catch。也使用了异步 NSURLConnection,但它被更改为同步,因为主线程被阻塞,直到异步完成为止。下一步是检查我今天将要执行的 NSOperation 使用情况,并将返回结果。
  • 在这种情况下,您确定是 requestAndParse 方法导致了泄漏吗?
  • 我现在已经实现了 NSOperationQueue 并且它似乎仍然在不断增加内存。我会将我如何使用 FMDB 发回给 OP。
猜你喜欢
  • 1970-01-01
  • 2012-05-22
  • 1970-01-01
  • 1970-01-01
  • 2013-01-07
  • 1970-01-01
  • 2017-05-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多