个性化阅读
专注于IT技术分析

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

Objective-C中的内存管理

内存管理是管理对象的生命周期,并在不再需要它们时释放它们的编程规则。管理对象内存是性能问题;如果应用程序没有释放不需要的对象,那么它的内存占用会增加,性能会受到影响。然而,垃圾收集在iOS中是不可用的。iOS通过引用计数管理内存。让我们来学习一下。

引用计数

如果某人拥有一个对象,这意味着该对象是有用的,因此系统不应该释放该对象。当没有人需要它时,它就会消失。基于这个规则, iOS通过引用计数来管理内存。每次对象添加一个所有者,引用计数加1,反之亦然。如果引用计数等于0,则应调用对象的dealloc方法。同时,我们可以使用这些方法来改变引用计数:

对象操作 方法操作结果
创建并拥有对象alloc new copymutablecopy创建对象并将引用计数设置为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也必须被释放。这就形成了一个不可解的循环。

强引用循环retain-cycle

如何避免强引用循环?

苹果在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;
}
赞(0)
未经允许不得转载:srcmini » iOS内存管理:引用计数、Runloop、AutoreleasePool和引用循环

评论 抢沙发

评论前必须登录!