> 文章列表 > iOS实用内存管理

iOS实用内存管理

iOS实用内存管理

文章目录

  • 使用setter/getter方法使内存管理更容易
    • 使用setter/getter方法来设置属性值
    • 不要在init方法和dealloc方法中使用存取器方法
    • 使用weak来避免循环引用
    • 避免正在使用的对象被dealloc
    • 集合持有它们包含的对象
    • 持有规则使用retain计数来实现

[[内存管理]]
您可以采取一些实际步骤来使内存管理更容易,并帮助确保您的程序保持可靠和健壮,同时最大限度地减少其资源需求

使用setter/getter方法使内存管理更容易

如果你的类具有对象属性,你必须确保在使用时任何被赋值的对象都没有被释放,因此当对象被设置时,你必须声明其所有权,还必须确保释放当前持有任何值的持有权
有时它可能看起来很乏味或迂腐,但如果你始终如一地使用存取器(setter/getter)方法,内存管理出现问题的可能性就会大大降低。如果您在整个代码中使用retainrelease实例变量,肯定做错了事情。

@interface counter:NSObject
@property (nonatomic, retain) NSNumber *count;
@end

该属性声明了两种存取方法,通常,您应该要求编译器合成这些方法
看看它们如何实现是有益的。
在“获取”访问器中,您只需返回合成的实例变量,因此无需retainrelease

- (NSNumber *)count {return _count;
}

在“设置”方法中,如果其他人都遵循相同的规则,您必须假设新的计数可能随时被处理,因此您必须通过发送retain消息来获得对象的所有权,以确保它不会被处理。您还必须通过向其发送release消息来放弃对此处旧计数对象的所有权。(Objective-C中允许向nil发送消息,因此如果_count尚未设置,则实现仍将有效。)您必须在[newCount retain]之后发送此信息,以防两者是相同的对象——您不想无意中导致它被dealloc。

- (void)setCount:(NSNumber *)newCount {[newCount retain];[_count release];// Make the new assignment._count = newCount;}

使用setter/getter方法来设置属性值

假设您想实现一种方法来重置计数器。你有几个选择。第一个实现使用alloc创建NSNumber实例,因此您可以通过release来平衡它

- (void)reset {NSNumber *zero = [[NSNumber alloc] initWithInteger:0];[self setCount:zero];[zero release];}

第二个使用方便的构造函数来创建一个新的NSNumber对象。因此,没有必要retain或release消息

- (void)reset {NSNumber *zero = [NSNumber numberWithInteger:0];[self setCount:zero];}

请注意,两者都使用设置存取器方法。

以下几乎肯定会在简单情况下正常工作,但即使避开访问器方法可能很诱人,但这样做几乎肯定会在某个阶段导致错误(例如,当您忘记retain或release时,或者如果实例变量的内存管理语义发生变化)。

- (void)reset {NSNumber *zero = [[NSNumber alloc] initWithInteger:0];[_count release];_count = zero;}

另请注意,如果您使用键值观察 ,那么以这种方式更改变量不符合KVO。
[[KVO]]

不要在init方法和dealloc方法中使用存取器方法

唯一不应该使用存取器方法的来设置实例变量的地方是init方法和dealloc方法,要使用表示零的数字对象初始化计数器对象,您可以实现以下init方法:

- init {self = [super init];if (self) {_count = [[NSNumber alloc] initWithInteger:0];}return self;}

要允许用零以外的计数初始化计数器,您可以实现initWithCount:方法,如下所示:

- initWithCount:(NSNumber *)startingCount {self = [super init];if (self) {_count = [startingCount copy];}return self;}

由于Counter类有一个对象实例变量,您还必须实现一个dealloc方法。它应该通过向它们发送发布消息来放弃对任何实例变量的所有权,并最终调用super的实现:

- (void)dealloc {[_count release];[super dealloc];}

使用weak来避免循环引用

持有对象会创建对该对象的strong引用。在释放所有强引用之前,对象不能被dealloc。因此,如果两个对象可能有循环引用,就会出现一个循环引用的问题——也就是说,它们彼此之间有很强的引用(要么直接引用,要么通过其他对象的链,每个对象都有很强的引用,可以追溯到第一个)。
![[Pasted image 20230422210512.p
图1所示的对象关系说明了潜在的循环引用。文档对象对文档中的每个页面都有一个页面对象。每个Page对象都有一个属性,可以跟踪它在哪个文档中。如果文档对象对Page对象有强引用,而Page对象对文档对象有强引用,则两个对象都无法取消分配。在页面对象被释放之前,文档的引用计数不能变为零,在文档对象被释放之前,页面对象不会被释放。
![[Pasted image 20230422210512.png]]
循环引用问题的解决方案是使用weak引用。weak引用是一种非拥有关系,其中源对象不保留其具有引用的对象。
然而,为了保持对象图的完整性,必须在某个地方有强引用(如果只有弱引用,那么页面和段落可能没有任何所有者,因此将被dealloc)。因此,cocoa建立了一个惯例,即“父”对象应该保持对其“子”的强烈引用,而“子”应该对他们的“父”有微弱的引用。
因此,在图1中,doc对象强引用(保留)其page对象,但page对象对doc对象的强烈引用(不保留)doc对象
cocoa中弱引用的例子包括但不限于表格数据源、大纲视图项目、通知观察员以及杂项目标和代表。
您需要小心向仅持有弱引用的对象发送消息。如果您在取消分配后向对象发送消息,您的应用程序将崩溃。当对象有效时,您必须有明确定义的条件。在大多数情况下,弱引用对象意识到其他对象对它的弱引用,就像循环引用一样,并负责在解除分配时通知其他对象。例如,当您在通知中心注册对象时,通知中心会存储对该对象的弱引用,并在发布适当的通知时向其发送消息。当对象被重新分配时,您需要在通知中心取消注册,以防止通知中心向不再存在的对象发送任何进一步的消息。同样,当委托对象被解除分配时,您需要通过向另一个对象发送带有nil参数的setDelegate:消息来删除代理链接。这些消息通常从对象的dealloc方法发送。

避免正在使用的对象被dealloc

Cocoa持有规则规定,接收对象通常应在整个调用方法的范围内保持有效。还应该可以从当前范围内返回接收的对象,而不必担心它被释放。对象的getter方法返回缓存的实例变量或计算值对您的应用程序来说应该无关紧要。重要的是,该对象在您需要它时仍然有效。
这条规则偶尔会有例外,主要属于两类之一。

  • 当一个对象从其中一个基本集合类中删除时。
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.

当对象从其中一个基本集合类中删除时,它会发送release(而不是autorelease)消息。如果集合是已删除对象的唯一所有者,则已删除对象(示例中的heisenObject)将立即dealloc

  • 当父对象被dealloc
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.

在某些情况下,您从另一个对象检索对象,然后直接或间接释放父对象。如果释放父级导致其被dealloc,并且父项是子项的唯一所有者,那么子项(示例中的heisenObject)将同时dealloc(假设在父项的dealloc方法中发送释放而不是自动释放消息)。

为了防止这些情况,您在收到 heisenObject 后保留它,并在完成后释放它。例如:

heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];

集合持有它们包含的对象

当您将对象添加到集合(如数组、字典或集合)时,集合将拥有它。当对象从集合中删除或集合本身被释放时,集合将放弃所有权。因此,例如,如果您想创建一个数字数组,您可以执行以下任一操作:

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];[array addObject:convenienceNumber];
}

在这种情况下,您没有调用alloc,因此无需调用release。没有必要保留新数字(convenienceNumber),因为数组会这样做。

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];[array addObject:allocedNumber];[allocedNumber release];
}

在这种情况下,您确实需要在for循环范围内向allocatedNumber发送发布消息,以平衡分配。由于数组在addObject:添加时保留了数字,因此当它在数组中时,它不会被释放。
要理解这一点,请将自己置于实施收集类的人的位置。你想确保没有你照顾的对象从你下面消失,所以你在它们传入时向它们发送retain消息。如果它们被删除,您必须发送用于平衡的release消息,并且任何剩余的对象都应该在您自己的dealloc方法中发送release消息。

持有规则使用retain计数来实现

持有规则通过引用计数来实现——在保留方法之后通常称为“保留计数”。每个对象都有一个保留计数。

当您创建对象时,它的保留计数为1。

当您向对象发送保留消息时,其保留计数会增加1。

当您向对象发送发布消息时,其保留计数将减少1。

当您向对象发送自动发布消息时,其保留计数在当前自动发布池块的末尾递减1。

如果对象的保留计数减少到零,它将被dealloc。

重要信息:没有理由明确询问对象的保留计数是什么(请参阅retainCount)。结果往往具有误导性,因为您可能不知道哪些框架对象保留了您感兴趣的对象。在调试内存管理问题时,您应该只关注确保您的代码遵守所有权规则。