关于iOS内存管理

Posted

技术标签:

【中文标题】关于iOS内存管理【英文标题】:about iOS memory management 【发布时间】:2012-04-25 04:56:34 【问题描述】:

我有一个视图控制器显示一个表格视图,见下文:

//.h file
@interface CoreDataViewController : UIViewController<UITableViewDataSource, UITableViewDelegate>
    //NSArray property
    @property (retain, nonatomic) NSArray *arr;
@end

//.m file
- (void)viewDidLoad 
    //fetch data from core data, pass to arr property
    //context is a instance of NSManagedObjectContext
    arr = [context executeFetchRequest:request error:nil];


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section    
    if (arr == nil) 
        return 0;
    
    return [arr count]; //program stop here, nothing showed up in output console

xcode Profile(Instrument) 告诉我在 return [arr count] 处有一个僵尸对象; 我对后台发生的事情感到困惑,也许属性 arr 是由 ios 发布的,但是该属性在 .h 文件中有一个保留关键字。

如果 NSArray 像这样替换为 NSMutableArray,我发现一个解决方案可以解决这个问题:

@interface CoreDataViewController : UIViewController<UITableViewDataSource, UITableViewDelegate>
    //change to NSMutableArray
    @property (retain, nonatomic) NSMutableArray *arr;
@end

- (void)viewDidLoad 
    //convert NSArray to NSMutableArray
    arr = [[context executeFetchRequest:request error:nil] mutableCopy];

mutableCopy 方法中是否有保留或自动释放?

【问题讨论】:

【参考方案1】:

根据Basic Memory Management Rules,是的,mutableCopy 增加了保留计数。底线,名称以 allocnewcopymutableCopy 开头的方法都返回一个具有 +1 保留计数的对象(即,它将为您保留,因此您取得所有权,并且在非 ARC 项目中,您负责手动发布它)。 executeFetchRequest 方法没有,所以它(可以)返回零保留计数,除非你拥有它,否则你不能依赖它,即你通过对你的保留做一些有效地 +1 保留计数拥有。

现在,很明显,您假设因为您将您的财产定义为保留财产,所以它将为您保留。但是您必须使用系统生成的 setter 来执行此操作,但您的初始代码示例不会执行此操作。相反,您自己直接访问 ivar,绕过 setter。如果你想获得所有权,增加你的属性所暗示的保留计数,你应该调用默认的 setter:

[self setArr:[context executeFetchRequest:request error:nil]];

或使用等效的点表示法:

self.arr = [context executeFetchRequest:request error:nil];

但是当您单独使用 ivar arr 时(没有点或 self setArr 语法),它绕过了 setter 方法。这不是一个好的做法,因为你没有做必要的保留。在这个例子中(因为你知道 arr 还没有值),理论上你可以这样做:

arr = [[context executeFetchRequest:request error:nil] retain];

但如果 arr 可能已经有一个指向另一个数组的指针,那么你真的想要:

[release arr];
arr = [[context executeFetchRequest:request error:nil] retain];

使用 setter 是安全的,并且可以避免这种愚蠢行为。看看Declared Properties,了解retain 的使用如何转换为什么setter 代码,我认为这可能更有意义。

更新:

顺便说一句,正如其他人指出的那样,虽然上面的代码解决了你的数组没有被保留的问题,但是一旦你成功地进行了保留,你必须记住在你的 dealloc 中释放它。

【讨论】:

非常漂亮和详细的 cmets @Robert Ryan。我习惯于用 C# 语法思考,所以忽略 ivar 和属性之间的区别。谢谢你的耐心回答,现在我知道了。 遗憾的是,Objective C 中 ivars 和属性之间的区别是不必要的模糊。更广泛地说,内存处理是这种语言的一个根本弱点。程序员真的不必担心这类事情。 ARC 让语言更接近于更合适的东西(如果你还没有看过它,你真的应该看过),但这种语言仍然感觉有点不合时宜。【参考方案2】:

如果你使用 mutableCopy,你需要自己释放一个变量。它将保留计数增加 1,但不会减少。 可能你可以使用 like -

arr = [[[context executeFetchRequest:request error:nil] mutableCopy] autorelease];

【讨论】:

无意冒犯,但这种语法会遇到我认为@rock 试图解决的确切问题,即在调用他的tableView:numberOfRowsInSection 之前,变量已在他身上释放。他真的想保留变量(通过显式保留他将 ivar 设置为的内容,或使用属性的 setter 方法)。但对你来说,他不能忘记释放他在deallocviewDidUnload 中保留的这个变量。 好吧,现在我记得 Apple 的指南中提到从复制方法返回的对象必须明确释放,谢谢你们俩 @rock - 如果您的问题得到解决,那么您可以接受适当的答案并关闭此问题。【参考方案3】:

是的,属性arr在.h文件中有一个retain关键字,但是你直接使用了ivar,你没有使用setter方法设置arr。所以它不会增加 arr 的保留计数,您应该将代码更改为:

//.m file
- (void)viewDidLoad 
  //fetch data from core data, pass to arr property
  //context is a instance of NSManagedObjectContext
  self.arr = [context executeFetchRequest:request error:nil];

在dealloc方法中释放arr

- (void)dealloc

  [arr release];
  [super dealloc];

无需将 NSArray 更改为 NSMutableArray

【讨论】:

谢谢@Tranz,我明白了。你们在理解内存管理、self 关键字和 getter setter 方面帮助了我很多。我永远不会犯这样的错误

以上是关于关于iOS内存管理的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发ARC内存管理技术要点

iOS 内存管理之属性关键字

ios 关于堆 栈,变量存储等问题解析

深入浅出iOS系统内核(3)— 内存管理

iOS 内存管理

iOS内存管理