美文网首页
内存管理篇(一)

内存管理篇(一)

作者: 请叫我大帅666 | 来源:发表于2019-05-26 15:59 被阅读0次

理解引用计数

前言: OC 语言使用引用计数来管理内存,也就是说, 每个对象都有个可以递增或递减的计数器. 如果想使某个对象继续存活, 那就递增其引用计数; 用完了之后, 就递减其引用计数. 当计数变为0时, 对象就会被销毁了.

ARC实际上就是一种引用计数机制.

要点

1. 引用计数机制通过可以递增递减的计数器来管理内存. 对象创建好之后, 其保留计数至少为1. 若保留计数为正, 则对象继续存活. 当保留计数降为0时, 对象就被销毁了.

2. 在对象生命周期中, 其余对象通过引用来保留或释放此对象. 保留与释放操作分别会递增或递减保留计数.

1.引用计数工作原理

在引用计数架构下, 对象有个计数器, 用以表示当前有多少个事物想令此对象继续存活下去. 在OC中, 叫"保留计数"(retain count), 也可以叫"引用计数"(reference count).

NSObject协议声明了以下三个方法用于操作计数器,以递增或递减其值:

  • retain : 递增保留计数
  • release : 递减保留计数
  • autorelease : 待稍后清理"自动释放池"(autorelease pool)时, 再递减保留计数.

查看保留计数的方法: retainCount, 这方法不太管用,在调试时也是如此,苹果公司并不建议使用

对象创建出来时,其保留计数至少为1. 若想令对象继续存活,则调用retain方法. 若不再使用此对象, 就调用release或autorelease方法. 当保留计数为0时,对象就被回收了, 系统会将其占用的内存标记为"可重用". 此时,所有指向该对象的引用就无效了.

在对象生命周期中, 其保留计数时而递增, 时而递减, 最终归零

应用程序在其生命周期中会创建很多对象, 这些对象都相互联系着. 例如下图中: 对象B与对象C 都引用了对象A. 若对象B与对象C都不再使用对象A, 则其保留计数减为0, 于是对象A被销毁了.

对象图里所有指向对象A的引用均释放之后, 对象A所占内存亦可回收

图中还有其他对象引用对象B与对象C, 而应用程序里又有另外一些对象引用其他对象. 如果按"引用树"回溯,最终会发现一个"根对象". 在iOS应用程序中, 是UIApplication对象. Max OS中则是NSApplication对象,两者都是应用程序启动时创建的单例.

下面这段代码有助于理解这些方法的用法:

NSMutableArray *array = [[NSMutableArray alloc] init];
    
NSNumber *number = [[NSNumber alloc] initWithInt:2019];
    
[array addObject:number];
[number release];
    
// do something with 'array'
    
[array release];

在ARC下, 无法编译release方法; 在OC中, 调用alloc方法所返回的对象由调用者拥有. 对象引用计数加1, 注意: 这并不是说对象此时的保留计数必定是1. 在alloc 或 initWithInt: 方法实现代码中, 也许还有其他对象引用了此对象, 所以其保留计数可能会大于1. 能够肯定的是: 其保留计数至少为1. 保留计数的概念应该这样理解, 绝不应该说保留计数一定是某个值, 只能说你执行的操作递增或递减了该计数.

创建完数组后, 把number对象加入其中, 调用数组的 addObject: 方法时, 数组也会在 number上调用 retain 方法, 来继续保留该对象. 这时计数至少为2. 接着释放 number 对象, 保留的计数至少为1, 调用 release 之后, 已经无法保证所指的number 对象是否存活, 也就不能正常使用 number变量了. 当前本例中,数组还引用着number对象, 在调用release之后依然存活, 然而绝不应假设此对象一定存活, 也就是说不能像下面那样编码:

[number release];
NSLog(@"number= %@", number);

如果由于某些原因, 其引用计数减为0, 那么number对象所占内存也许会被回收, 此时调用 NSLog 可能会使程序崩溃!!!

为避免不经意间使用了无效对象, 一般用完release 之后都会清空指针. 保证不会出现可能指向无效对象的指针, 这种指针通常称为"悬挂指针".
可以照下面编码来防止此情况发生:

[number release];
number = nil;

2.属性存取方法中的内存管理

访问属性时, 会用到相关实例变量的获取方法及设置方法. 若属性为"strong"关系, 则设置的属性值会保留.
例: 有个名叫 foo 的属性由名为 _foo 的实例属性所实现, 那么,该属性设置方法会是这样的:

- (void)setFoo:(id)foo {
    [foo retain]; // 保留新值
    [_foo release]; // 释放旧值
    _foo = foo;
}

此方法将保留新值并释放旧值, 然后更新实例变量, 令其指向新值. 顺序很重要, 假如还未保留新值就先把旧值释放了, 而且两个值又指向同一个对象, 那么, 先执行release操作就可能导致系统将此对象永久回收. 而后续的retain操作无法令这个已经回收的对象复生, 于是实例变量也就成了悬挂指针.

3. 自动释放池

在OC的引用计数架构中, 自动释放池是一个重要特性. 调用release 会立刻递减对象的保留计数(而且可能令系统回收此对象), 有时候可以改为调用 autorelease, 此方法会在稍后递减计数, 通常是在下一次"事件循环"时递减, 不过也可能执行更早些.

此特性很有用, 尤其在方法中返回对象时; 以下面方法为例:

- (NSString *)stringValue {
    NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@",self];
    return str;
}

此时返回的str对象其保留计数比期望值多1, 因为调用alloc会令保留计数加1, 而又没有与之对应的释放操作. 有retain,就应该有对应的release, 将其抵消. 这并不是说保留计数就一定是1, 可能大于1(这取决于initWithFormat:方法内部实现).

但是,不能在方法内释放str,否则还没等方法返回, 系统就把该对象回收了. 这里应该用autorelease, 它会在稍后释放对象, 从而给调用者留下足够长时间在其需要时先保留返回值. 也可以这样理解: 此方法可以保证对象在跨越"方法调用边界"后仍然存活.

- (NSString *)stringValue {
    NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@",self];
    return [str autorelease];
}

修改之后,stringValue方法把NSString对象返回给调用者时,此对象必然存活. 所以能像下面使用:

NSString *str = [self stringValue];
NSLog(@"the string is %@",str);

由于返回的str对象将于稍后自动释放,多出来的那一次retain操作到时自然就会抵消,无需再执行内存管理操作. 因为自动释放池中的释放操作要等到下一次事件循环时才会执行.

autorelease能延长对象生命期,使其在跨越方法调用边界后依然可以存活一段时间.

4. 循环引用

使用引用计数机制时, 经常要注意一个问题就是: 循环引用; 也就是呈环状相互引用的多个对象. 这将导致内存泄漏, 因为循环中的对象其保留计数不会降为0.

通常采用"弱引用"来解决此问题, 或是从外界命令循环中的某个对象不再保留另外一个对象. 这两种方法都能打破保留环, 避免内存泄漏.

相关文章

  • iOS内功篇:内存管理

    iOS内功篇:内存管理 iOS内功篇:内存管理

  • iOS内存管理篇(二)---NSAutoreleasePool/

    前言:上一篇内存管理里面, iOS内存管理篇(一)--alloc/reatain/release/dealloc方...

  • 内存管理篇(一)

    理解引用计数 前言: OC 语言使用引用计数来管理内存,也就是说, 每个对象都有个可以递增或递减的计数器. 如果想...

  • iOS/OS X内存管理(二):借助工具解决内存问题

    上一篇博客iOS/OS X内存管理(一):基本概念与原理主要讲了iOS/OSX 内存管理中引用计数和内存管理规则,...

  • 笔记五:JavaScript性能优化

    内存管理 内存管理介绍 内存:由可读写单元组成,表示一片可操作空间 管理:认为的去操作一篇空间的申请、使用和释放 ...

  • 内存管理篇

    对于不同场景,系统提供的有不同的内存管理方案,大致有如下三种: -TaggedPointer (对于一些小对象,比...

  • iOS 内存管理-应用篇

    内存管理部分建议先看上一篇iOS 内存管理-基础篇 在for循环中alloc图片数据等呢村消耗较大的场景中手动插入...

  • Objective-C 内存管理基础

    前言 之前的两篇拙文C语言-内存管理基础、C语言-内存管理深入 介绍了关于C语言在内存管理方面的相关知识。但是对于...

  • 第3篇-C堆内存管理-原理篇

    前言 我前面一篇详细介绍了堆内存管理的有关概念,你往下读该篇的内容,我确信你已经阅读了我前面2篇有关堆内存管理的随...

  • IOS内存管理(二)借助工具解决内存问题

    转载自iOS/OS X内存管理(二):借助工具解决内存问题 上一篇博客IOS内存管理(一)基本概念与原理主要讲了i...

网友评论

      本文标题:内存管理篇(一)

      本文链接:https://www.haomeiwen.com/subject/nplxtctx.html