iOS内存管理(引用计数、Runloop、AutoreleasePool和引用循环)

Objective-C中的内存管理内存管理是管理对象的生命周期,并在不再需要它们时释放它们的编程规则。管理对象内存是性能问题;如果应用程序没有释放不需要的对象,那么它的内存占用会增加,性能会受到影响。然而,垃圾收集在iOS中是不可用的。iOS通过引用计数管理内存。让我们来学习一下。
引用计数【iOS内存管理(引用计数、Runloop、AutoreleasePool和引用循环)】如果某人拥有一个对象,这意味着该对象是有用的,因此系统不应该释放该对象。当没有人需要它时,它就会消失。基于这个规则, iOS通过引用计数来管理内存。每次对象添加一个所有者,引用计数加1,反之亦然。如果引用计数等于0,则应调用对象的dealloc方法。同时,我们可以使用这些方法来改变引用计数:

对象操作 方法操作结果
创建并拥有对象alloc  new  copymutablecopy创建对象并将引用计数设置为1
拥有对象retain引用计数 + 1
释放对象release引用计数 – 1
清理对象dealloc 当引用计数为0时,调用它
我们可以通过以下方法了解一个对象的生命周期:
iOS内存管理(引用计数、Runloop、AutoreleasePool和引用循环)

文章图片
在创建和初始化阶段之后,只要对象的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调用应用程序事件处理程序,使事件对象可用。
iOS内存管理(引用计数、Runloop、AutoreleasePool和引用循环)

文章图片
处理程序可以将对象放入自动释放池,或者使用其他对象放入自动释放池的对象。
iOS内存管理(引用计数、Runloop、AutoreleasePool和引用循环)

文章图片
在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也必须被释放。这就形成了一个不可解的循环。
iOS内存管理(引用计数、Runloop、AutoreleasePool和引用循环)

文章图片
如何避免强引用循环?苹果在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; }

    推荐阅读