iOS 中的 Sqlite 内存问题

发布于 2024-12-09 15:38:01 字数 2162 浏览 1 评论 0原文

我花了几个小时试图解决这个问题,但我已经放弃了;我不知道出了什么问题。

在我的应用程序中,我执行嵌套 SQL 操作来正确设置所有对象。由于某些原因,有时 sqlite3 对象无法正确释放,导致内存飙升。我知道这是正确使用 sql3_close 和 sql3_finalize 的问题。然而,正如你将看到的,我认为我正确地使用了它们。

这是问题的根源:

- (NSArray *) getAllSessions {
    if (sqlite3_open(dbPath, &db) == SQLITE_OK) { 
        if (sqlite3_prepare_v2(db, query_stmt, -1, &statement, NULL) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {

                //I found out that doing something like that
                //toAdd.in_loc = [self getIndoorLocationWithId:[NSNumber numberWithInt:(int)sqlite3_column_int(statement, 6)]];
                //messes the memory up all the time
                //but doing that works OK:
                NSNumber *_id = [[NSNumber alloc]  initWithInt:(int) sqlite3_column_int(statement, 5)];
                toAdd.out_loc = [self getOutdoorLocationWithId:_id];
                [_id release];

                //So I did the same with the second one, but this one messes memory up:
                NSNumber *id2 = [[NSNumber alloc] initWithInt:(int)sqlite3_column_int(statement, 6)];
                toAdd.in_loc = [self getIndoorLocationWithId:id2];
                [id2 release];
            }
            sqlite3_finalize(statement);
        }
        sqlite3_close(db);
    } 
}

所以这是一个混乱内存的问题:

- (IndoorLocation *) getIndoorLocationWithId:(NSNumber *) locId {
    if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK) { 
        if (sqlite3_prepare_v2(db, query_stmt, -1, &statement, NULL) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {  
                //if I comment the thing below it works
                NSNumber *_id = [[NSNumber alloc]  initWithInt:(int) sqlite3_column_int(statement, 5)];
                toReturn.outLoc = [self getOutdoorLocationWithId:_id];
                [_id release];

            }
            sqlite3_finalize(statement);
        }
        sqlite3_close(db);
    }
}

因此,在混乱内存的问题中,我使用与第一次完全相同的函数(getOutdoorLocationwithId),以相同的方式,但它不工作时,sqlite3 对象无法正确释放。

我希望你能理解我的问题,这让我发疯!

I spent hours trying to fix that but I've just given up; I have no idea what's wrong.

In my app, I do nested SQL operations to set all my objects correctly. For some reason, sometimes the sqlite3 objects do not get release properly, causing the memory to go up the roof. I understand it is a problem with using correctly sql3_close and sql3_finalize. However, as you will see, I think I have used them correctly.

Here is the source of the problem:

- (NSArray *) getAllSessions {
    if (sqlite3_open(dbPath, &db) == SQLITE_OK) { 
        if (sqlite3_prepare_v2(db, query_stmt, -1, &statement, NULL) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {

                //I found out that doing something like that
                //toAdd.in_loc = [self getIndoorLocationWithId:[NSNumber numberWithInt:(int)sqlite3_column_int(statement, 6)]];
                //messes the memory up all the time
                //but doing that works OK:
                NSNumber *_id = [[NSNumber alloc]  initWithInt:(int) sqlite3_column_int(statement, 5)];
                toAdd.out_loc = [self getOutdoorLocationWithId:_id];
                [_id release];

                //So I did the same with the second one, but this one messes memory up:
                NSNumber *id2 = [[NSNumber alloc] initWithInt:(int)sqlite3_column_int(statement, 6)];
                toAdd.in_loc = [self getIndoorLocationWithId:id2];
                [id2 release];
            }
            sqlite3_finalize(statement);
        }
        sqlite3_close(db);
    } 
}

So here is the one that messes memory up:

- (IndoorLocation *) getIndoorLocationWithId:(NSNumber *) locId {
    if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK) { 
        if (sqlite3_prepare_v2(db, query_stmt, -1, &statement, NULL) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {  
                //if I comment the thing below it works
                NSNumber *_id = [[NSNumber alloc]  initWithInt:(int) sqlite3_column_int(statement, 5)];
                toReturn.outLoc = [self getOutdoorLocationWithId:_id];
                [_id release];

            }
            sqlite3_finalize(statement);
        }
        sqlite3_close(db);
    }
}

So in the one that messes memory up, I use exactly the same function as the first time (getOutdoorLocationwithId), in the same way but it doesn't work, sqlite3 objects don't get released properly.

I hope you understand my problem, this is driving me nuts!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

败给现实 2024-12-16 15:38:01

在这样的单用户应用程序中,无需不断打开和关闭。这与基于服务器的应用程序不同,在基于服务器的应用程序中,您与多个用户建立连接池,而保持连接可能会破坏池并损害可扩展性。在手机应用程序中打开它并保持打开状态。完成后关闭它。至少,在递归调用中执行此操作。

更糟糕的是,您在递归调用中打开和关闭 - 只需将其打开即可。

另外:

  • 您没有检查 Finalize 的返回代码
  • 您没有检查 close 的返回代码。
  • 您正在准备(编译)语句,但没有保存它们 - 对保存的语句调用重置并再次执行它。

考虑使用 FMDB - 它是一个很好的包装器。

顺便说一句,这里有一个更丰富、更持久的结束语,但不要在每次通话中都使用它。当你结束或你的应用程序进入后台时关闭它......这是我的,类似于 fmdb 所做的。

- (void)close
{
    if (_sqlite3)
    {
        ENInfo(@"closing");
        [self clearStatementCache];

        int rc = sqlite3_close(_sqlite3);
        ENDebug(@"close rc=%d", rc);

        if (rc == SQLITE_BUSY) 
        { 
            ENError(@"SQLITE_BUSY: not all statements cleanly finalized");

            sqlite3_stmt *stmt; 
            while ((stmt = sqlite3_next_stmt(_sqlite3, 0x00)) != 0) 
            {
                ENDebug(@"finalizing stmt");
                sqlite3_finalize(stmt); 
            }

            rc = sqlite3_close(_sqlite3);
        }

        if (rc != SQLITE_OK)
        {
            ENError(@"close not OK.  rc=%d", rc);
        }

        _sqlite3 = NULL;
    }
}

In a single user app like this, there's no need to continually open and close. This is not like a server based app where you have connection pooling with multiple users where holding the connection can defeat the pool and hurt scalability. In a phone app open it and hold it open. close it when you're done. At a minimum, do that within a recursive call.

To make it worse, you're opening and closing within recursive calls - just leave it open.

Also:

  • You're not checking the return codes of finalize
  • You're not checking the return codes of close.
  • You're preparing (compiling) the statments but you're not saving them off - call reset on the saved statement and execute it again.

Consider using FMDB - it's a good wrapper.

BTW, here's a richer and more durable close but don't use it on every call. Close it when you're ending or your app is going into the background ... This is mine which is similar to what fmdb does.

- (void)close
{
    if (_sqlite3)
    {
        ENInfo(@"closing");
        [self clearStatementCache];

        int rc = sqlite3_close(_sqlite3);
        ENDebug(@"close rc=%d", rc);

        if (rc == SQLITE_BUSY) 
        { 
            ENError(@"SQLITE_BUSY: not all statements cleanly finalized");

            sqlite3_stmt *stmt; 
            while ((stmt = sqlite3_next_stmt(_sqlite3, 0x00)) != 0) 
            {
                ENDebug(@"finalizing stmt");
                sqlite3_finalize(stmt); 
            }

            rc = sqlite3_close(_sqlite3);
        }

        if (rc != SQLITE_OK)
        {
            ENError(@"close not OK.  rc=%d", rc);
        }

        _sqlite3 = NULL;
    }
}
手心的海 2024-12-16 15:38:01

我遇到了同样的问题。
当我使用并打开 FMDB 时,它工作正常。但在其他回调中它失败并抛出“关闭泄漏声明”的异常。最后我发现这是因为我保留了 [FMDatabase databaseWithPath:dbPath] 中的指针,并且该指针是一个自动释放对象。我通过以下方式解决了这个问题:

FMDatabase *db = [[FMDatabase databaseWithPath:dbPath] retain];

当你想关闭数据库时:
db [关闭];
[数据库发布];
db = 零;

这样,您不必总是为每个操作打开和关闭数据库,应该由一个管理器对象来解释它。当管理器启动时,数据库始终打开,直到管理器停止。

I met the same issue.
When i use and open FMDB, it works OK. but in other callbacks it failed and threw exception of "Closing leaked statement." Finally I found that's because i kept the pointer from [FMDatabase databaseWithPath:dbPath] and the pointer is a autorelease object. I fixed the issue by this:

FMDatabase *db = [[FMDatabase databaseWithPath:dbPath] retain];

and when you want to close the database:
db [close];
[db release];
db = nil;

By this, you don't always open and close database for each operation, the should be a manager object accounting for it. When the manager starts, the database is open always until the manager stops.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文