为 Objective-C 数据对象分配内存的问题

Posted

技术标签:

【中文标题】为 Objective-C 数据对象分配内存的问题【英文标题】:Problem with allocating memory for an Objective-C data object 【发布时间】:2011-04-15 00:33:45 【问题描述】:

我已经编写了几个月的 Objective-C 编程,到目前为止做得很好,无需发布任何问题。这将是我的第一个。问题是我在其中一种方法中从数据对象收到内存泄漏警告。我可以看到问题是我在没有释放它的情况下向它发送了一个分配,但我不知道如何让它将对象保留在内存中。如果我取出分配器,程序就会崩溃。如果我把它留在里面,它会泄漏内存。这是有问题的方法:

+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure 
Feature *newFeature = [[self alloc] init];
newFeature.featureID = fID;
newFeature.featureName = fName;
newFeature.featureSecure = fSecure;

return [newFeature autorelease];

这个方法被我的视图控制器中的另一个方法调用。该方法如下:

+ (NSMutableArray*) createFeatureArray 

NSString *sqlString = @"select id, name, secure from features";
NSString *file = [[NSBundle mainBundle] pathForResource:@"productname" ofType:@"db"];
sqlite3 *database = NULL;
NSMutableArray *returnArray = [NSMutableArray array];

if(sqlite3_open([file UTF8String], &database) == SQLITE_OK) 

    const char *sqlStatement = [sqlString UTF8String];
    sqlite3_stmt *compiledStatement;

    if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) 

        while(sqlite3_step(compiledStatement) == SQLITE_ROW) 

            Feature *myFeature = [Feature featureWithID:sqlite3_column_int(compiledStatement,0) 
                                                   name:[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)]
                                                 secure:sqlite3_column_int(compiledStatement,2)];

            [returnArray addObject:myFeature];

        
    
    // Release the compiled statement from memory
    sqlite3_finalize(compiledStatement);

sqlite3_close(database);
return returnArray;

我已经尝试了几件事,例如创建一个 featureWithFeature 类方法,这将允许我在调用方法中分配初始化功能,但这也会导致程序崩溃。

如果您需要任何说明或代码的任何其他部分,请告诉我。提前感谢您的帮助。

更新:2011 年 4 月 14 日

阅读前两个回复后,我实施了该建议,发现程序现在崩溃了。我完全不知道如何追查罪魁祸首。希望这会有所帮助,我也发布了视图控制器中的调用方法:

- (void)setUpNavigationButtons 
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
int i = 0;


for (i = 0; i < [featureArray count]; i++) 
    Feature *myFeature = [featureArray objectAtIndex:i];
    CGRect buttonRect = [self makeFeatureButtonFrame:[featureArray count] withMember:i];

    UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [aButton setFrame:buttonRect];

    [aButton addTarget:self action:@selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
    [aButton setTitle:[NSString stringWithFormat:@"%@",myFeature.featureName] forState:UIControlStateNormal];
    aButton.tag = myFeature.featureID;

    [self.view addSubview:aButton];


注意:这些方法的发布顺序与调用它们的顺序相反。最后一个方法调用第二个方法,后者又调用第一个方法。

更新:我已经更新了这些函数以显示其中的内容:下面,我将发布对象的头文件 - 也许这会有所帮助

@interface Feature : NSObject 
    int         featureID;
    int         featureSecure;
    NSString    *featureName;


@property (nonatomic, assign) int featureID;
@property (nonatomic, assign) int featureSecure;
@property (nonatomic, retain) NSString *featureName;

- (id) init;

- (void) dealloc;

+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure;



@end

@interface FeatureController : NSObject 




- (id) init;

- (void) dealloc;

+ (NSMutableArray*) createFeatureArray;

+ (Feature*) getFeatureWithID:(int)fetchID;

@end

【问题讨论】:

为了记录,你可以保留你的原始实现(返回一个拥有的对象,即没有autorelease)并将你的方法重命名为newFeatureWith…。包含new 的方法名称被理解为返回调用者拥有的对象。 returnArray 被自动释放两次。 一般来说,如果您遇到此类问题,删除所有不必要的代码以重现相同的错误非常有用。它 (1) 帮助您缩小问题范围,可能自己解决它; (2) 让其他人更容易理解和帮助您。 我同意 - 我只是不想遗漏任何可能导致我不知道会产生影响的问题。过去,当我在这里搜索解决方案时,我总是看到“我们需要查看更多代码”,下次我会尝试更加明确,看看我是否能找到一个快乐的媒介。 【参考方案1】:

便捷方法应遵循返回自动释放对象的约定。改变这个:

+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure 
Feature *newFeature = [[self alloc] init];
...    
return newFeature;

到:

+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure 
Feature *newFeature = [[self alloc] init];
...    
return [newFeature autorelease];

【讨论】:

我已经按照你的建议实现了这个,但是现在程序一碰到调用这些方法的视图控制器就会崩溃。如果我完整地发布这两个类的代码会有帮助吗? (它们不是很大)。 +createFeatureArray 返回一个无主对象,这是正确的。调用该方法的代码是否保留了它返回的数组,并对其建立了长期的所有权声明?换句话说,通过您所做的更改,这些方法正确地保持了内存管理规则的结束 - 但如果调用这些方法的代码没有保持其结束,事情仍然可能出错。跨度> 我刚刚在上面发布了一个更新,你可以看到。我相信数组上的 alloc init 和底部的 release 正在做你描述的事情。 是的。内存管理在编写时是平衡的,至少在您目前显示的代码中是这样。创建和释放一个新数组有点多余——你可以简单地说NSMutableArray *featureArray = [FeatureController createFeatureArray],而不必担心释放它。但是你的代码中的+alloc-release 是平衡的,所以这不是你崩溃的原因。崩溃日志显示什么? 另一个想法 - featureName 的属性声明是什么样的?由于它是一个对象,它应该是 copyretain - 而不是 assign【参考方案2】:

您的方法的名称 - +featureWithID:name:secure: - 表明它返回一个不属于调用者的对象。相反,它返回一个已保留的对象,因此调用者拥有并必须释放该对象。要解决此问题(以及您的泄漏),只需将 return newFeature 替换为 return [newFeature autorelease]

您无需再做任何事情,因为您自己的代码不需要长期的所有权声明,并且您要向其中添加对象的数组将管理它自己的所有权声明。

【讨论】:

我已经按照你的建议实现了这个,但是现在程序一碰到调用这些方法的视图控制器就会崩溃。我刚刚在上面发布了一个更新,您也可以在其中看到初始调用方法在做什么。【参考方案3】:

+createFeatureArray 中,您已过度释放数组:

+ (NSMutableArray*) createFeatureArray 
    …
    NSMutableArray *returnArray = [[[NSMutableArray alloc] init] autorelease];
    …
    return [returnArray autorelease];

在第一行中,您使用了+alloc,因此您拥有该数组。然后您使用了-autorelease,因此您不再拥有该阵列。这意味着您不应该发送 -release-autorelease 给它,您正在 return 行中这样做。

您可以通过将这些行更改为:

+ (NSMutableArray*) createFeatureArray 
    …
    NSMutableArray *returnArray = [NSMutableArray array];
    …
    return returnArray;

此外,除非与调用者相关的是数组是可变的,否则您应该更改该方法以返回 NSArray 而不是 NSMutableArray。即使方法声明声明返回类型为NSArray,您也可以保持代码不变,即返回一个可变数组。


至于您的便利构造函数,基本上有两种选择,具体取决于您是要返回拥有的对象还是非拥有的对象:

如果你想返回一个拥有的对象,用+alloc+new 分配它并返回它而不自动释放它。您的方法名称应包含new,例如+newFeatureWithId:…

如果您想返回一个不属于调用者的对象,请使用+allocnew 分配它,并在将其返回给调用者之前/之后自动释放它。您的方法名称不应包含newalloccopy


-setUpNavigationButtons 中,通过+createFeatureArray 获得一个非拥有的数组,基于它分配一个可变数组,然后释放可变数组而不添加或删除元素。当您需要添加/删除元素时,可变数组很有意义。如果您没有此需求,您可以将方法更改为:
- (void)setUpNavigationButtons 
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
…
// [featureArray release];

您将删除该 [featureArray release],因为您在该方法中不拥有 featureArray


编辑:-setUpNavigationButtons 中,您将保留您创建的按钮,并在您释放它后不久。在那个特定的方法中,这些是幂等操作——它们本身没有错,但根本没有必要。您可以用

替换该代码
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
…
[self.view addSubview:aButton];
// [aButton release];

即不保留也不释放。

【讨论】:

更改是 setUpNavigationButtons 和 createFeatureArray 工作得很好。但是,当我对 featureWithID 进行任何这些更改时,程序会崩溃。我尝试保持名称相同并自动释放返回,并尝试将方法重命名为 newFeatureWithID(并更改标头和调用者),它在打开视图控制器时立即崩溃。 @CarltonSmith 据我所知,您在问题上发布的代码 + 我提出的更改是正确的。只是为了确定:featureNamecopy(或 retain)声明的属性吗? 这就是我声明的方式:@property (nonatomic, retain) NSString *featureName; 另外两个是这样的:@property (nonatomic, assign) int featureID; @CarltonSmith 没错(虽然字符串属性通常是copy),这让我相信错误出在其他地方,可能在您的视图控制器实现中。崩溃日志可能会有所帮助。

以上是关于为 Objective-C 数据对象分配内存的问题的主要内容,如果未能解决你的问题,请参考以下文章

[学习笔记—Objective-C]《Objective-C-基础教程 第2版》第九章 内存管理

在objective-c中为原始类型分配内存的最佳实践

ios学习路线—Objective-C(堆(heap)和栈(stack))

如何确定哪些对象持有对导致 Objective-c 内存泄漏的其他对象的引用?

Objective-C:内存泄漏与自动释放的多重分配?

如何为objective-C中的类中的数组分配内存?