当前位置:   article > 正文

【Effective Objective-C】——内存管理_objective-c有哪几种内存管理方法?

objective-c有哪几种内存管理方法?

理解引用计数

参考博客:

【iOS开发】—— 自动引用计数初步学习
【iOS开发】——引用计数的简单了解

引用计数工作原理

在引用计数架构下,对象有个计数器,用以表示当前有多少个事物想令此对象继续存活下去。在Objective-C中叫做“保留计数”,也可以叫做“引用计数”。
NSObject协议声明了以下三个方法用来操作计数器:

Retain  递增保留计数
release 递减保留计数
autorelease 待稍后清理“自动释放池”时,再递减保留计数
  • 1
  • 2
  • 3

在这里插入图片描述

注意:有时调用完release方法后,为避免在不经意间使用了无效对象,要清空指针,比如:

[number release];
number = nil;
  • 1
  • 2

这样就能保证不会出现可能指向无效对象的“悬挂指针”。

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

调用retain方法可以保留对象。若属性为“strong”关系,则设置的属性值会保留。比如,有个名叫foo的属性由名为_foo的实例对象所实现,那么该属性的设置方法会是这样:

- (void)setFoo:(id)foo {
	[foo retain];
	[_foo release];
	_foo = foo;
}
  • 1
  • 2
  • 3
  • 4
  • 5

此方法就是保留新值,释放旧值,然后更新实例变量,令其指向新值。注意顺序。

自动释放池

调用autorelease会在稍后递减计数,通常是在下一次“事件循环”时递减,也可能更早些。此方法在返回对象时更应该使用它,比如:

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

此时返回的str对象其保留计数比期望值要多一,因为调用alloc会令保留计数加一,而其后又没有与之对应的释放操作。所以要设法将其减1。但是不能在方法内释放str,否则还没等方法返回,系统就把该对象回收了。这里我们就可以使用autorelease

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

这样stringValue方法把NSString对象返回给调用者时,此对象必然存活。并且返回的str对象将于稍后自动释放。
由此可加:autorelease能延长对象生命期,使其在跨越方法调用边界后仍然可以存活一段时间。

保留环

使用引用计数机制时,经常要注意一个问题就是“保留环”,也就是成环状相互引用的多个对象,这将导致内存泄漏,因为循环会导致对象其保留计数不会降为0。
在这里插入图片描述
在垃圾收集环境中,通常将这种情况视为“孤岛”,此时,垃圾收集器会把这三个对象都回收走。而在Objective-C的引用计数架构中,则无法享受到这一便利。通常采用“弱引用”来解决此问题,或是从外界命令循环中的某个对象不再保留另外一个对象

要点:

  • 引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活。当保留计数降为0时,对象就被销毁了。
  • 在对象生命期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。

以ARC简化引用计数

1.由于ARC会自动执行retain、release、autorelease、dealloc等操作,所以在ARC下直接调用这些方法是非法的。
2. 不能重写retain、release、autorelease,因为这些方法不会被直接调用,ARC会调用与其等价的底层函数

使用ARC时必须遵循的方法命名规则

  1. 使用以下词语开头的方法名返回的对象归调用者所有
  • alloc
  • new
  • copy
  • mutableCopy

归调用者所有意思是:调用上述四个词语开头的方法的那段代码要负责释放方法所返回的对象,也就是说,这些对象的保留计数计数是正值,而调用了这四种方法的那段代码要将其中一次保留操作抵消掉。
若方法名不以上述四个词语开头,则表示其所返回的对象并不归调用者所有。

  1. 除了会自动调用“保留”和“释放”方法外,ARC还可以执行一些手工操作很难完成的优化。
  2. 在ARC环境下编译代码时,必须考虑“向后兼容性”,以兼容那些不使用ARC的代码,其实ARC的简化操作是因为其调用的特殊函数,它会把autorelease方法改为调用objc_autoreleaseReturnValue函数,把retain方法改为objc_retainAutoreleaseReturnValue。下面用一段代码掩饰ARC是如何通过特殊函数来优化程序的:
    在这里插入图片描述

为了求得最佳效率,这些特殊函数的实现代码都因处理器而异。下面这段伪代码描述了其中的步骤:
在这里插入图片描述

将内存管理交由编译器和运行期组件来做,可以使代码得到多种优化。

变量的内存管理语义

ARC也会处理局部变量与实例变量的内存管理。默认情况下,每个变量都是指向对象的强引用

  1. ARC会用一种安全的方法来实现setter方法:先保留新值,在释放旧值,最后设置实例变量。用了ARC之后,不需要考虑“边界情况”。
  2. 在应用程序中,可用下列修饰符来改变局部变量与实例变量的语义:
  • _ _strong:默认语句,保留此值。
  • _ _unsafe_unretained:不保留此值,这么做可能不安全,因为等到再次使用变量时,其对象可能已经回收了。
  • _ _weak:不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会清空。
  • _ _autoreleasing:把对象“按引用传递”给方法时,使用这个特殊的修饰符。此值在方法返回时自动释放。
    我们经常会给局部变量加上修饰符,所以打破由“块”所引入的“保留环”。块会自动保留其所捕获的全部对象,而如果这其中有某个对象又保留了块本身,这就可能导致“保留环”,可以使用_ _weak局部变量来打破这种保留环。例如:
    在这里插入图片描述

ARC如何清理实例变量

ARC可以对实例变量进行内存管理,要管理器内存,ARC就要在“回收分配给对象的内存”时生成必要的清理代码。凡是具备强引用的变量,都必须释放,ARC会在dealloc方法中插入这些代码。ARC会借用Objective-C++的一些特性来生成清理例程。回收Objective-C++对象时,待回收的对象会调用所有C++对象的析构函数。编译器如果发现某个对象含有C++对象,就会生成名为.cxx_destruct的方法。而ARC借助此特性,在该方法中生成清理内存所需的代码。如果存在非Objective- C对象,ARC会在生成的清理内存代码中调用超类的dealloc方法。
ARC会自动生成回收对象时回收对象所执行的代码,所以通常无须再编写dealloc方法。

覆写内存管理方法

不使用ARC时,可以覆写内存管理方法。但使用ARC环境时,不可以覆写,以避免干扰到ARC分析对象生命期的工作。

要点:

  • 有 ARC 之后,程序员就无须担心内存管理问题了。使用 ARC 来编程,可省去类中的许多“样板代码”。
  • ARC管理对象生命期的办法基本上就:在合适的地方插入“保留”及“释放”操作。在ARC环境下,变量的内存管理语义可以通过修饰符指明,而原来则需要手工执行“保留”及“释放”操作。
  • 由方法所返回的对象,其内存管理语义总是通过方法名来体现。ARC将此确定为开发者必须遵守的规则。
  • ARC只负责管理Objective-C对象的内存。尤其要注意:CoreFoundation 对象不归 ARC管理,开发者必须适时调用CFRetain/CFRelease

在dealloc方法中只释放引用并解除监听

应该在dealloc方法中做些什么呢?主要就是释放对象所拥有的引用,其次把原来配置过的观测行为都清理掉。比如CoreFoundation对象必须手工释放,用NSNotificationCenter给对象订阅过某种通知,那么也要注销,否则继续给回收后的对象发通知将会导致程序崩溃。所以dealloc方法可以这样来写:

- (void)dealloc {
	CFRelease(coreFoundationObject);
	[[NSNotificationCenter defaultCenter] removeObserve:self];
  • 1
  • 2
  • 3

注意:如果手动管理引用计数而不使用ARC的话,那么最后还需调用“[super dealloc]”。ARC会自动执行此操作,所以一般不选择手动管理,因为手动管理需要将所拥有的全部Objective-C对象逐个释放。

要点:

  • 在dealloc方法里,应该做的事情就是释放指向其他对象的引用,并取消原来订阅的“键值观测”(KVO)或NSNotificationCenter 等通知,不要做其他事情。
  • 如果对象持有文件描述符等系统资源,那么应该专门编写一个方法来释放此种资源。这样的类要和其使用者约定:用完资源后必须调用close 方法。
  • 执行异步任务的方法不应在dealloc里调用;只能在正常状态下执行的那些方法也不应在dealloc里调用,因为此时对象已处于正在回收的状态了。

编写“异常安全代码”时留意内存管理问题

Objective-C的错误模型表明,异常只应该发生严重错误后抛出,不过有时仍然需要编写代码来捕获并处理异常。
先介绍一下@try、@catch、@finally

@try {
 ... 逻辑处理
 ...执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容
}

@catch {
  ... 异常捕捉
   ...除非try里面执行代码发生了异常,否则这里的代码不会执行
}

@finally{
  ... 逻辑执行结果
  ...不管什么情况都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

@try块中如果先保留了某个对象,然后在释放它之前又抛出了异常,除非@catch块能处理此问题,否则对象所占内存就将泄漏。
例如下面这段代码:
在这里插入图片描述

如果doSomethingThatMayThrow抛出异常,由于异常会令执行过程终止并跳至catch块,导致之后的[object release]代码不会执行,这就回导致内存泄漏。解决办法就是使用@finally块,无论是否抛出异常,都会执行其中的代码,所以将上面的代码改为下面这样:
在这里插入图片描述

如果要手动管理引用计数,而且必须捕获异常,那么就要设法保证所编代码能把对象正确清理干净。若使用ARC且必须捕获异常,则需打开编译器的-fobjc-arc-exceptions标志。

要点:

  • 捕获异常时,一定要将try块内所创立的对象清理干净。
  • 在默认情况下,ARC不生成安全处理异常所需的清理代码。开启编译器标志后,可生成这种代码,不过会导致应用程序变大,而且会降低运行效率。

以弱引用避免保留环

在对象图里,会经常出现一种情况,就是几个对象之间都以某种方式相互引用,从而形成“环”,这种情况下就会造成内存泄漏避免保留环最佳方式就是弱引用。这种引用通常来表示“非拥有关系”。将属性声明为unsafe_unretained即可。但是会因为属性特质中的unsafe_unretained一词表明属性值可能不安全,而且不归此实例所有,在修饰的属性所指的那个对象被回收后,再在其上调用方法就会使应用程序崩溃。但是OC还有一项与ARC相伴的运行期特性,可以令开发者安全的使用弱引用:这就是weak属性特质。它与unsafe_unretained的作用完全相同。然而只要系统把属性回收,属性值就会设为nil

要点:

  • 将某些引用设为weak,可避免出现“保留环”。
  • weak引用可以自动清空,也可以不自动清空。自动清空是随着ARC而引入的新特性。由运行期系统来实现。在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。

以“自动释放池块”降低内存峰值

有一项特性叫做“自动释放池”,释放对象有两种方式:一种是调用release方法,使其保留计数立即递减;另一种是调用autorelease方法,将其加入自动“释放池”中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空释放池,系统就会向其中的对象发送release消息。
创建自动释放池所用语法:

@autoreleasepool {
	//...
}
  • 1
  • 2
  • 3
  1. 自动释放池可以嵌套,系统在自动释放对象时,会把它放到最内层的池里。这样嵌套使用的好处就是:可以借此控制应用程序的内存峰值,使其不致过高。
  2. 在循环内部增加一个自动释放池,将循环内的代码包裹在“自动释放池块”中,那么在循环中自动释放的对象就会放在这个池子里,而不是线程的主池里。
  3. 内存峰值是指应用程序在某个特定时段内的最大内存用量。
  4. 自动释放池机制就像“”一样。系统创建好自动释放池之后,就将其推入栈中,而清空自动释放池,就相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。

要点:

  • 自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里。
  • 合理运用自动释放池,可降低应用程序的内存峰值。
  • @autoreleasepool这种新式写法能创建出更为轻便的自动释放池。

用“僵尸对象”调试内存管理问题

Cocoa提供了“僵尸对象”这个方便的功能,在运行期系统会把所有已经回收的实例转化成特殊的“僵尸对象”,而不会真正回收它们。僵尸对象收到消息后,会抛出异常,因此僵尸对象是调试内存管理问题的最佳方式。

  1. 将NSZombieEnabled环境变量设为YES,即刻开启此功能。
  2. 在Xcode中也可以打开此选项:编辑应用程序里的Scheme,在对话框左侧选择“Run”,然后切换至“Diagnostics”分页,最后勾选“Enable Zombie Objects”选项。
    在这里插入图片描述

要点

  • 系统在回收对象时,可以不将其真的回收,而是把它转化为僵尸对象。通过环境变量NSZombieEnabled可开启此功能。
  • 系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能够响应所有的选择子,响应方式为:打印一条包含消息内容及其接受者的消息,然后终止应用程序。

不要使用retainCount

NSObject协议中定义了下列方法,用于查询对象之前的保留计数:

- (NSUInteger)retainCount
  • 1

然而ARC已经将此方法废弃了。所以不要再去使用retainCount就好了。

要点:

  • 对象的保留计数看似有用,实则不然,因为任何给定时间点上的“绝对保留计数”都无法反映对象生命期的全貌。
  • 引入ARC之后,retainCount方法就正式废止了,在ARC调用该方法会导致编译器报错。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/829382
推荐阅读
相关标签
  

闽ICP备14008679号