Objective-C中的内存管理
内存管理是管理对象的生命周期,并在不再需要它们时释放它们的编程规则。管理对象内存是性能问题;如果应用程序没有释放不需要的对象,那么它的内存占用会增加,性能会受到影响。然而,垃圾收集在iOS中是不可用的。iOS通过引用计数管理内存。让我们来学习一下。
引用计数
如果某人拥有一个对象,这意味着该对象是有用的,因此系统不应该释放该对象。当没有人需要它时,它就会消失。基于这个规则, iOS通过引用计数来管理内存。每次对象添加一个所有者,引用计数加1,反之亦然。如果引用计数等于0,则应调用对象的dealloc方法。同时,我们可以使用这些方法来改变引用计数:
对象操作 | 方法 | 操作结果 |
---|---|---|
创建并拥有对象 | alloc new copy mutablecopy | 创建对象并将引用计数设置为1 |
拥有对象 | retain | 引用计数 + 1 |
释放对象 | release | 引用计数 – 1 |
清理对象 | dealloc | 当引用计数为0时,调用它 |
我们可以通过以下方法了解一个对象的生命周期:
在创建和初始化阶段之后,只要对象的retain count大于0,该对象就保留在内存中。程序中的其他对象可以通过发送retain或copy来表示对某个对象的所有权,然后通过向该对象发送release来放弃该所有权。当对象收到其最终释放消息时,其retain count降为0。因此,调用对象的dealloc方法,释放任何对象或它已分配的其他内存,并销毁对象。
在过去,开发人员需要手动管理引用计数,我们称之为手动保持释放(MRR),现在苹果重新命令自动引用计数(ARC),这意味着你不需要关心这些方法以上表,当你写代码。ARC可以帮助您在程序编译时自动添加内存管理方法。
Runloop运行循环和AutoreleasePool自动释放池
Runloop是一个用于管理线程的循环。应用程序工具包为一个应用程序创建至少一个NSRunloop实例。应用程序运行在这个循环执行后, 如下图所示,当一个触摸事件发生时, Cocoa Touch框架检测事件,创建一个事件对象, 然后生成自动分配和初始化一个池, 基本上是一个NSAutoreleasePool对象(如果你使用ARC, 你不能直接使用autorelease池。相反,你应该使用@autoreleasepool块)。然后Cocoa touch调用应用程序事件处理程序,使事件对象可用。
处理程序可以将对象放入自动释放池,或者使用其他对象放入自动释放池的对象。
在MRC中,我们可以使用autorelease方法将对象放到autorelease池中,立即调用release;将retainCount减少1,如果变为0则调用dealloc。
引用循环
先看以下代码:
#import <Foundation/Foundation.h>
@class RetainCycleClassB;
@interface RetainCycleClassA : NSObject
@property (nonatomic, strong) RetainCycleClassB *objectB;
@end
--------------------------------------------------------------
#import "RetainCycleClassA.h"
#import "RetainCycleClassB.h"
@implementation RetainCycleClassA
- (instancetype)init
{
if (self = [super init]) {
self.objectB = [[RetainCycleClassB alloc] initWithClazzA:self];
}
return self;
}
@end
--------------------------------------------------------------
#import "RetainCycleClassA.h"
@interface RetainCycleClassB : NSObject
@property (nonatomic, strong) RetainCycleClassA *objectA;
- (instancetype)initWithClazzA:(RetainCycleClassA*)objectA;
@end
---------------------------------------------------------------
#import "RetainCycleClassB.h"
@implementation RetainCycleClassB
- (instancetype)initWithClazzA:(RetainCycleClassA *)objectA
{
if (self = [super init]) {
self.objectA = objectA;
}
return self;
}
@end
当你运行这些代码时,不会发现objectA和objectB release。这两个实例形成了引用循环retain cycle。
Retain cycle是内存管理中一个普遍存在的问题。如果有两个对象A和B,并且它们彼此拥有对方,它们都不能被释放,当生命周期结束时,将导致内存泄漏。
就像下图中的第一幅图一样。ObjectA的强指针指向ObjectB, ObjectB的强指针指向ObjectA。在ARC中,强指针意味着拥有和引用计数+ 1。这就带来了一个问题,如果你想让ObjectA的引用计数等于0, ObjectB必须被释放,而你想让ObjectB被释放,ObjectA也必须被释放。这就形成了一个不可解的循环。
如何避免强引用循环?
苹果在ARC中提供了弱指针。弱指针有两个特点:
- 它不会让引用计数加1。
- 当对象的生命周期结束时,该对象将为nil。
请看上图中的第二张图。弱指针代替强指针。尽管ObjectB只有一个指向ObjectA的指针,但ObjectB并不拥有ObjectA,引用计数也不会增加。这样,它们的记忆就会正常地释放出来。
强引用循环的三种情况
委托/代理delegate
如果属性delegate被声明为强类型,则会导致retain cycle。
@property (nonatomic, weak) id <RetainCycleDelegate> delegate;
MyViewController *viewController = [[MyViewController alloc] init];
viewController.delegate = self; //suppose self is id<RetainCycleDelegate>
[self.navigationController pushViewController:viewController animated:YES];
block
typedef void (^RetainCycleBlock)();
@property (nonatomic, copy) RetainCycleBlock aBlock;
if (self.aBlock) {
self.aBlock();
}
当块复制时,块将强引用指向内部块所有变量。这个类将块作为自己的属性变量,在这个类中调用内部块self。这就形成了一个引用循环。
self.testObject.aBlock = ^{
[self doSomething];
};
我们可以用弱引用来打破这个循环
__weak typeof(self) weakSelf = self;
self.testObject.aBlock = ^{
__strong typeof(weakSelf) strongSelft = weakSelf;
[strongSelft doSomething];
};
NSTimer
当我们将self设置为NStimer回调的目标时,它将生成retain cycle。所以我们需要设置timer invalidate并设置timer nil,当timer完成任务时。
- (void)dealloc {
[self.myTimer invalidate];
self.myTimer = nil;
}
评论前必须登录!
注册