在Objective-C内存管理中,每一个对象都有属于本身的计数器:若是想让某个对象继续存活(例如想对该对象进行引用),就递增它的引用计数;当用完它以后,就递减该计数;当没人引用该对象,它的计数变为0以后,系统就把它销毁。 html
这个,就是引用计数在其中充当的角色:用于表示当前有多少个对象想令此对象继续存活程序中;java
2.引用计数的介绍:编程
引用计数(Reference Count),也叫保留计数(retain count),表示对象被引用的次数。一个简单而有效的管理对象生命周期的方式。安全
3.引用计数的工做原理:数据结构
流程参考图以下:多线程
(图片表格取自《编写高质量iOS与OS X代码的52个有效方法》一书)
框架
4.操做引用计数的方法:函数
因此,调用release后会有2种状况:工具
调用前计数>1,计数减1;oop
调用前计数<1,对象内存被回收;
Eg: [object retainCount]; //获得object的引用计数
retain、release、autorelease详解:
retain做用:
调用后计数+1,保留对象操做。可是当对象被销毁、内存被回收的时候,即便使用retain也再也不有效;
autorelease做用:
autorelease不当即释放,而是注册到autoreleasepool(自动释放池)中,等到pool结束时释放池再自动调用release进行释放工做。
autorelease看上去很像ARC,可是实际上更相似C语言中的自动变量(局部变量),当某自动变量超出其做用域(例如大括号),该自动变量将被自动废弃,而autorelease中对象实例的release方法会被调用;[与C不一样的是,开发者能够设定变量的做用域。]
释放时间:每一个Runloop中都建立一个Autorelease pool(自动释放池),每一次的Autorelease,系统都会把该Object放入了当前的Autorelease pool中,并在Runloop的末尾进行释放,而当该pool被释放时,该pool中的全部Object会被调用Release。 因此,通常状况下,每一个接受autorelease消息的对象,都会在下个Runloop开始前被释放。
例如可用如下场景:(须要从ARC改成使用手动管理的能够作以下的设置: 在Targets的Build Phases选项下Compile Sources下选择要不使用ARC编译的文件,双击它,输入-fno-objc-arc便可使用MRC手工管理内存方式;)
-(NSString *)getSting { NSString *str = [[NSString alloc]initWithFormat:@"I am Str"]; return [str autorelease]; }
自动释放池中的释放操做会等到下一次时间循环时才会执行,因此调用如下:
NSString *str = [self getSting]; NSLog(@"%@",str);
返回的str对象得以保留,延迟释放。所以能够无需再NSLog语句以前执行保留操做,就能够将返回的str对象输出。
因此可见autorelease的做用是能延长对象的生命期。使其在跨越方法调用边界后依然能够存活一段时间。
release做用:
release会当即执行释放操做,使得计减1;
有这样一种状况:当某对象object的引用计数为1的时候,调用“[object release];”,此时若是再调用NSLog方法输出object的话,可能程序就会崩溃,固然只是有可能,由于对象所占内存在“解除分配(deallocated)”以后,只是放回“可用内存池(avaiable pool)”,可是若是执行NSLog时,还没有覆写对象内存,那么该对象依然有效,因此程序有可能不会崩溃,因而可知,因过早地释放对象而致使的bug很难调试。
为避免这种状况,通常调用完对象以后都会清空指针:"object = nil",这样就能保证不会出现指向无效对象的指针,也就是悬挂指针(dangling pointer);
悬挂指针:指向无效对象的指针。
那么,向已经释放(dealloc)的对象发送消息,retainCount会是多少?
原则是不能够这么作。由于该对象的内存已经被回收,而咱们向一个已经被回收的对象发了一个 retainCount 消息,因此它的输出结果应该是不肯定的,例如为减小一次内存的写操做,不将这个值从 1 变成 0,因此很大可能输出1。例以下面这种状况:
Person *person = [[Person alloc] init]; //此时,计数 = 1 [person retain]; //计数 = 2 [person release]; //计数 = 1 [person release]; //极可能计数 = 1;
虽然第四行代码把计数1release了一次,原理上person对象的计数会变成0,可是实际上为了优化对象的释放行为,提升系统的工做效率,在retainCount为1时release系统会直接把对象回收,而再也不为它的计数递减为0,因此一个对象的retainCount值有可能永远不为0;
所以,无论是否为ARC的开发环境中,也不推荐使用retainCount来作为一个对象是否存在于内存之中的依据。
2、ARC
1.背景:
ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。
即便2014 年的 WWDC 大会上推出的Swift 语言,该语言仍然使用 ARC 技术做为其管理方式。
2.ARC是什么?
须要注意的是,ARC并非GC(Garbage Collection 垃圾回收器),它只是一种代码静态分析(Static Analyzer)工具,背后的原理是依赖编译器的静态分析能力,经过在编译时找出合理的插入引用计数管理代码,从而提升iOS开发人员的开发效率。
Apple的文档里是这么定义ARC的:
“自动引用计数(ARC)是一个编译器级的功能,它能简化Cocoa应用中对象生命周期管理(内存管理)的流程。”
3.ARC在作什么?
在编译阶段,编译器将在项目代码中自动为分配对象插入retain、release和autorelease,且插入的代码不可见。
可是,须要注意的是,ARC模式下引用计数规则还起做用,只是编译器会为开发者分担大部分的内存管理工做,除了插入上述代码,还有一部分优化以及分析内存的管理工做。
做用:
4.ARC具体为引用计数作了哪些工做?
编译阶段自动添加代码:
编译器会在编译阶段以恰当的时间与地方给咱们填上本来须要手写的retain、release、autorelease等内存管理代码,因此ARC并不是运行时的特性,也不是如java中的GC运行时的垃圾回收系统;所以,咱们也能够知道,ARC实际上是处于编译器的特性。
例如:
-(void)setup { _person = [person new]; }
在手工管理内存的环境下,_person是不会自动保留其值,而在ARC下编译,其代码会变成:
-(void)setup { person *tmp = [person new]; _person = [tmp retain]; [tmp release]; }
固然,在开发工做中,retain和release对于开发人员来讲均可以省去,由ARC系统自动补全,达到一样的效果。
但实际上,ARC系统在自动调用这些方法时,并不经过普通的Objective-C消息派发控制,而是直接调用底层C语言的方法:
好比retain,ARC在分析到某处须要调用保留操做的地方,调用了与retain等价的底层函数 objc_retain,因此这也是ARC下不能覆写retain、release或者autorelease的缘由,由于这些方法在ARC历来不会被直接调用。
运行期组件的优化:
ARC是编译器的特性,但也包含了运行期组件,所执行的优化颇有意义。
例子:
person工厂方法personWithName能够获得一个person对象,在这里调用并赋值给person的一个实例_one:
_one = [person personWithName:@"name"];
可能会出现这种状况:
在personWithName方法中,返回对象给_one以前,为其调用了一次autorelease方法。
因为实例变量是个强引用,因此编译器会在设置其值的时候还须要执行一次保留操做。
person *tmp = [person personWithName:@"name"]; //在personWithName方法返回前已有调用一次autorelease方法进行保留操做; _one = [tmp retain];
很明显,autorelease与紧跟其后的retain是重复的。为提高性能,能够将两者删去,舍弃autorelease这个概念,而且规定返回对象的技术都比指望值多1,可是为了向后兼容非ARC等状况,ARC采起另一种方式:
ARC能够在运行期检测到这一对多余的操做。
而,设置并检测标志位,要比调用autorelease和retain更快,这就使得这一状况的处理获得优化。
修改2个函数后优化完整结果以下: 【例子来自《编写高质量iOS与OS X代码的52个有效方法》一书P126】
咱们能够经过两个函数的伪代码大体描述以下:
像是objc_autoreleaseReturnValue这个函数是如何检测方法调用者是否会马上保留对象呢,这就要交给处理器来解决了。
因为必须查看原始机器码指令方可判断出这一点须要处理器来定。
因此,其实只有编译器的做者才能知道这里是如何实现此函数的。
ARC的安全性:
在编写属性的设置方法(setter)时,若是使用手工管理方式,可能会须要以下编写:
-(void)setObject:(id)object { [_object release]; _object = [object retain]; }
可是这样写会出现问题:若是说新值object和实例变量_object的值是相同的,并且只有当前实例变量对象还在引用这个值,那么设置方法中的释放操做会使得该值保留计数为0,系统将其回收,因此接下来的保留操做,将会令应用程序崩溃。
而在使用ARC的环境下,就不可能会发送这样的的“边界状况”了:
刚才的代码在ARC下能够这样写(固然,咱们知道若是不须要覆写setter方法,也能够不编写此方法,直接使用"self.object = xxx"也能够安全地调用。):
-(void)setObject:(id)object
{
_object = object;
}
并且ARC会用一种安全的方式来设置:先保留新值,再释放旧值,最后设置实例变量。
在手工管理的状况下,咱们须要特别注意这种"边缘状况",可是ARC下,咱们就能够很轻松地编写这种代码了,而不用去考虑这种状况如何处理了。
总结:将内存管理交由编译器和运行期组件来作,可使代码获得多种优化,而上面是其中一种方式。
5.ARC下须要注意的规则
不能显式调用如下代码:
(NSZone:内存区)
不能再使用NSAutoreleasePool对象,ARC提供了@autoreleasepool块来代替它,这样更有效率;
关于dealloc:
6.全部权修饰符
oc编程中为了处理对象,可将变量类型定义为id类型或各类对象类型。使用这些限定符能够确切地声明对象变量和属性的生命周期;
所谓对象类型就是指向NSObject这样的oc类的指针,例如“NSObject *”。id类型用于隐藏对象类型的类名部分。至关于C语言中经常使用的“void *”;
ARC下,id类型和对象类型上必须附加全部权修饰符;
全部权修饰符一共有4种:
__strong:
强引用,能够引用别的对象为强引用,至关于retain的特性;代表变量持有alloc/new/copy/mutableCopy方法群建立的对象的强引用,强引用变量会在其做用域里被保留,在超出做用域后被释放,为默认的修饰符;
例如如下代码:
id objc = [[NSObject alloc] init];
实际上已被附上全部权修饰符:
id __strong objc = [[NSObject alloc] init];
__weak:
使用__strong,有可能2个对象相互强引用或者1个对象对自身强引用则会发生循环引用(以下图,或者叫保留环),因此当对象在超出其生存周期后,本应被系统废弃却仍然被引用者所持有,因此形成内存泄露(应当废弃的对象在超出生命周期后,继续存在);
而当咱们对可能会发送循环引用的对象进行__weak弱引用修饰,弱引用变量不会持有对象,且生成的对象会马上释放,可避免循环引用,而且弱引用还有另一个特色,若对象被系统回收,该弱引用变量将自动失效而且赋值为nil。
__unsafe_unretained: 不安全的全部权修饰符,ARC的内存管理是编译器的工做,而附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。与__weak做用同样,也能够避免循环引用;可是不一样的是,__unsafe_unretained属性的变量不会将变量设置为nil,而是就处于于悬挂状态;
__autoreleasing:在ARC中使用“@autoreleasepool块”来取代“NSAutoreleasePool”类对象的生成,经过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法;
Other:ARC须要注意的事项?
1.过分使用 block 以后,没法解决循环引用问题。
2.遇到底层 Core Foundation 对象,须要本身手工管理它们的引用计数时,咱们需转换关键字,做为桥接转换以解决 Core Foundation 对象与 Objective-C 对象相对转换的问题:
__bridge:使用__bridge标记能够在不修改相关对象的引用计数的状况下,将对象从Core Foundation框架数据类型转换为Foundation框架数据类型(反之亦然)。
__bridge_retained:会将相关对象的引用计数加 1,而且可以将Core Foundation框架数据类型对象转换为Foundation框架数据类型对象,并从ARC接管对象的全部权。
__bridge_transfer:能够将Foundation框架数据类型对象转换为Core Foundation框架数据类型对象,而且会将对象的全部权交给ARC管理,也就是说引用计数交由ARC管理;
总结:就推荐2本经典的书(估计不少人早就看完了😂 ),书本也好,pdf也好,建议看一下:
《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》
《Objective-C高级编程 iOS与OS X多线程和内存管理》