【问题标题】:iOS APP With ARC no leaking but live bytes increasing带有 ARC 的 iOS APP 没有泄漏但实时字节数增加
【发布时间】:2014-01-23 22:15:44
【问题描述】:

非常感谢@Hot Licks 的帮助,他指出了我的 sqlite3 代码有什么问题。我修改了代码,通过 sqlite3 增加的活动字节消失了。

对于其他新的ios开发者也可能面临这个问题,我把原来的问题留在了后面。

我的新问题是:仪器的每一代之间仍有一些增加的活动字节,但似乎所有对象都是由 ios SDK 的代码组成的,而不是我的代码。 那我应该不理会增加的事情,不用担心吗?

@Hot Licks 说我操作 UI 的方式可能有问题,所以我详细描述一下:

1) 我在 Xcode 5 中为 ipad 创建了一个主从应用程序;

2) 将master嵌入到Tab bar控制器中,并添加一个新的tab,所以master是一个有2个tab的tab bar控制器。所有这些事情都在故事板中完成。

3) 删除细节控制器中的默认标签。在详细视图中添加一个表格视图、一个文本视图和 3 个按钮。在 tableview 中添加一个原型单元格。所有这些事情都在故事板中完成。

4)在detailViewController.h中连接tableview,textview作为outlet。

@property (strong, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) IBOutlet UITextView *explanationText;

5)修改主控制器中的函数:“tableview:didSelectRowAtIndexPath”:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    LHBPoetry *poetry = [_searchResults objectAtIndex:indexPath.row];
    self.detailViewController.poetryId = poetry.poetryId;
}

6) 修改detail view controller的代码,请看后面的“detail controller's code的相关sn-p”

这里面有问题吗?

非常感谢您的帮助!!!

旧部分:

当我在模拟器中调试我的应用程序时,内存(实时字节)不断增加,但 Instruments 中没有泄漏。

我的问题是:

  1. 我的代码有什么问题?我想也许每次我点击一个项目时,都会重新创建详细信息的界面?
  2. 如何找到泄露的代码?
  3. 由于一些爱好者的帮助,我将我的问题缩小到关于从 sqlite3 获取数据的代码。这些代码有什么问题?
  4. 我创建了一个默认的主从应用程序,我多次单击master中的项目,它的活字节也增加了。那么这是否意味着我不需要担心这个问题?

我的app是master-detail app,使用ARC,SDK是iOS 7,使用Xcode 5写代码。

这个应用程序在做什么: 在左侧的主导航中,有很多项,在右侧的详细视图中有一个表格。当用户点击某个项目时,详细视图中的表格内容会发生变化。

问题是每次在主导航中点击一个item,内存会增加150K~300K左右。

代码:

主控制器代码的相关sn-p:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    LHBPoetry *poetry = poetryArray[indexPath.row];
    self.detailViewController.poetryId = poetry.poetryId;
}

详细控制器代码的相关sn-p:

@interface LHBDetailViewController (){
    LHBPoetry *poetry;
    NSArray *sentenceArray;
    PoetryDao *poetryDao;
    PoetryService *poetryService;
}

@property (strong, nonatomic) UIPopoverController *masterPopoverController;
- (void)configureView;


@end

@implementation LHBDetailViewController

#pragma mark - Managing the detail item

- (void)setPoetryId:(int)poetryId
{
    if (_poetryId != poetryId) {
        _poetryId = poetryId;

        // Update the view.
        [self configureView];
    }

    if (self.masterPopoverController != nil) {
        [self.masterPopoverController dismissPopoverAnimated:YES];
    }
}

- (void)configureView
{
    // Update the user interface for the detail item.

    if (self.poetryId > 0) {
        poetry = [poetryDao getPoetryById:self.poetryId];
    }else{
        poetry = [poetryDao getPoetryById:1];
    }

    if(poetry != nil){
        //custom title
        if(poetry.dynasty != nil){
            self.title = [NSString stringWithFormat:@"%@  [%@]%@", poetry.name, poetry.dynasty, poetry.author];
        }else{
            self.title = [NSString stringWithFormat:@"%@  %@", poetry.name, poetry.author];
        }

        //refresh sentenceArray
        sentenceArray = [poetryService changeStringToArray:poetry.content withSplitter:[LHBConstant getPoetrySplitter]];

    }else{
        //custom title
        self.title = @"";
    }

    [_tableView reloadData];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    poetryDao = [[PoetryDao alloc] init];
    poetryService = [[PoetryService alloc] init];

    [self configureView];
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"detailCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell...

    NSString *sentence = sentenceArray[indexPath.row];
    cell.textLabel.text = sentence;

    return cell;
}


@end

我读到了this article about using Heapshot Analysis to find a leak

在 Xcode 中,我使用 Product->Profile 打开仪器,然后选择 Memory -> Allocations。 然后我这样做:

  1. 在仪器中,点击“标记生成”;
  2. 在我的应用程序中,单击主视图中的一个项目。 重复这 2 个步骤几次。

我收到了results from instruments

开启一代,I got this

我比较了所有的世代,我发现每次单击主导航中的一个项目时,一个对象都会增加。

它指向sqlite3的代码:

这里是sn-p的代码:

-(LHBPoetry *) getPoetryById:(int) poetryId{
    sqlite3 *database;

    @try{
        //open database
        if(sqlite3_open([[LHBConstant dataFilePath] UTF8String], &database)!=SQLITE_OK){
            sqlite3_close(database);
            NSAssert(0, @"Failed to open database.");
        }

        //find in database
        NSString *query = @"SELECT id,name,author,dynasty, content, explanation, has_license, has_mastered FROM poetry where id = ?";
        sqlite3_stmt *statement;
        if(sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil)==SQLITE_OK){
            //bind parameter
            sqlite3_bind_int(statement, 1, poetryId);

            while (sqlite3_step(statement)==SQLITE_ROW) {
                int primaryId = sqlite3_column_int(statement, 0);
                char *name = (char *)sqlite3_column_text(statement, 1);
                char *author = (char *)sqlite3_column_text(statement, 2);
                char *dynasty = (char *)sqlite3_column_text(statement, 3);
                char *content = (char *)sqlite3_column_text(statement, 4);
                char *explanation = (char *)sqlite3_column_text(statement, 5);
                int hasLicense = sqlite3_column_int(statement, 6);
                int hasMastered = sqlite3_column_int(statement, 7);

                NSString *nameNS = [NSString stringWithUTF8String:name];
                NSString *authorNS = [NSString stringWithUTF8String:author];
                NSString *dynastyNS = dynasty == nil ? NULL : [NSString stringWithUTF8String:dynasty];
                NSString *contentNS = [NSString stringWithUTF8String:content];
                NSString *explanationNS = explanation == nil ? NULL : [NSString stringWithUTF8String:explanation];

                LHBPoetry *poetry = [[LHBPoetry alloc] initWithId:primaryId withName:nameNS withAuthor:authorNS withDynasty:dynastyNS withContent:contentNS withExplanation:explanationNS withLicense:hasLicense withMastered:hasMastered];

                return poetry;
            }
            sqlite3_finalize(statement);
        }else{
            NSLog(@"poetry getPoetryById fail. database is not ready.");
        }
    }
    @catch (NSException *e) {
        NSLog(@"%@", e);
    }
    @finally {
        sqlite3_close(database);
    }

    return nil;
}

LHBConstant中的方法dataFilePath是:

+(NSString *)dataFilePath{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [paths objectAtIndex:0];
    documentDirectory = [documentDirectory stringByAppendingString: @"/p140107"];
    return documentDirectory;
}

我也在仪器中使用“memory->leak”模板,没有泄漏。

谁能帮帮我? 非常感谢!

【问题讨论】:

  • 请注意IOSiOS 不同。 IOS 代表en.wikipedia.org/wiki/Cisco_IOSiOS 代表en.wikipedia.org/wiki/IOS
  • 如果您仔细研究仪器结果,您很可能会发现某些特定类别的对象正在积累越来越多的实例。什么样的对象可能会导致您遇到问题。
  • @HotLicks 非常感谢!正如我在这个问题中提到的那样,我试图查看仪器中的结果,我遵循了关于使用仪器查找泄漏的教程。但我是一个新的 ios 开发人员,我对此一无所知......
  • @abentotoro 尝试评论任何复杂的代码并运行您的项目,看看是否仍然遇到同样的问题
  • 因为前天我有同样的内存问题,我在编码时犯了一个简单的错误,导致 1gb 活动字节

标签: ios objective-c sqlite memory-leaks


【解决方案1】:

return poetry; -- 我不相信这样返回会导致@finally 子句执行。当然sqlite3_finalize(statement); 语句不会被执行。

【讨论】:

  • 非常感谢!!是的,finally 子句被执行了,但是 sqlite3_finalize(statement) 没有被执行。我更改了退货条款。 sqlite3 增加的活动字节数消失了。在这个修改之前,每一代之间的存活字节增加了180K,而在这个修改之后,存活字节只增加了90K。我检查了这 90K 的对象,似乎它们都是 ios SDK 的代码,而不是我的代码。那么这是否意味着我可以不用担心不断增加的活动字节数?
  • @abentotoro - 我怀疑你还有泄漏。 iOS 中很少有存储泄漏错误,因为它们在测试中相对容易发现。你描述它的方式(虽然相当模糊)让我怀疑你操作 UI 界面的方式存在错误。
  • 非常感谢!我更新了我在帖子中操作UI界面的方式,请问有什么问题吗?
【解决方案2】:

到目前为止,我已经意识到,如果视图控制器上有一个私有实例变量并且您弹出它,则视图控制器不会被释放。换句话说,一旦你有一个私有实例变量,视图控制器的dealloc 方法就不会被调用。例如,您有一个 UIView *_v 和 NSTimer *_t 作为私有实例变量,如下所示:

@implementation MyViewController{

    UIView *_v;
    NSTimer *_t;
}

我正在做的是在从视图层次结构中弹出视图控制器之前使它们为零。我认为 ARC 在需要内存后的某个时候仍然能够释放 MyViewController。

【讨论】:

  • 非常感谢!我尝试从 detailContoller.m 中删除我的所有私有实例,并将它们作为属性放入 detailController.h,这确实使事情变得正确。
【解决方案3】:

您有一些很难回答的问题。

由于一些爱好者的帮助,我将我的问题缩小到有关从 sqlite3 获取数据的代码上。这些代码有什么问题?

对于 Sqlite 和调试版本:

  1. 确保未定义NDEBUG
  2. 确保已定义DEBUG
  3. 使用SQLITE_DEBUG预处理器
  4. 使用SQLITE_MEMDEBUG预处理器

否则,SQLite 会自动进入“发布”模式(来自sqlite3.c,第 14816 行):

#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
# define NDEBUG 1
#endif

另外,来自sqlite3.c,第 7780 行:

/*
** Exactly one of the following macros must be defined in order to
** specify which memory allocation subsystem to use.
**
**     SQLITE_SYSTEM_MALLOC          // Use normal system malloc()
**     SQLITE_WIN32_MALLOC           // Use Win32 native heap API
**     SQLITE_ZERO_MALLOC            // Use a stub allocator that always fails
**     SQLITE_MEMDEBUG               // Debugging version of system malloc()
**
** On Windows, if the SQLITE_WIN32_MALLOC_VALIDATE macro is defined and the
** assert() macro is enabled, each call into the Win32 native heap subsystem
** will cause HeapValidate to be called.  If heap validation should fail, an
** assertion will be triggered.
**
** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as
** the default.

如果需要,您还可以使用 SQLITE_CONFIG_MALLOCsqlite3_config 来使用 Xcode 的内存管理器。看看sqlite3_mem_methods。它可能有助于跟踪。 此外,请确保您在来自 SQLite 的错误消息字符串上调用 sqlite3_free

最后,您可以查询 SQlite 的内存统计信息(来自sqlite3.c,第 1550 行):

** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
** <dd> ^This option takes single argument of type int, interpreted as a
** boolean, which enables or disables the collection of memory allocation
** statistics. ^(When memory allocation statistics are disabled, the
** following SQLite interfaces become non-operational:
**   <ul>
**   <li> [sqlite3_memory_used()]
**   <li> [sqlite3_memory_highwater()]
**   <li> [sqlite3_soft_heap_limit64()]
**   <li> [sqlite3_status()]
**   </ul>)^
** ^Memory allocation statistics are enabled by default unless SQLite is
** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory
** allocation statistics are disabled by default.
** </dd>

【讨论】:

    猜你喜欢
    • 2011-11-23
    • 1970-01-01
    • 2012-02-25
    • 2019-11-11
    • 2010-11-02
    • 2017-10-03
    • 2013-08-19
    • 1970-01-01
    • 2011-06-20
    相关资源
    最近更新 更多