本文首发于公众号【程序员华仔】
------------------------
本文主要讲解 自动引用计数和循环引用 这两个大问题。
对于自动引用计数,没有什么争议。
而对于循环引用,这里主要是讲Object-C语言下的循环引用, 因为据我了解,Swift语言下也有循环引用。这两者根本原因是一致的,但解决方法有很大的差异。 所以这里特别说明是Object-C语言下的循环引用。对于Swift下的循环引用,以后再讲解。
自动引用计数
概念
说自动引用计数之前,先说下引用计数,引用计数是苹果公司设计出来,用来跟踪和管理App的内存情况的一套机制。它的运行机制大概是,当创建一个类对象,引用计数就为1,retain一次,计数 1,release一次,计数-1, 当计数减为0,系统就释放该对象,内存也回收,实现了对App的内存管理。
引用计数又分为:手动引用计数(Manual Reference Count)和自动引用计数(Auto Reference Count),前者简称MRC,后者为ARC。
MRC在iOS开发前期使用。主要由开发人员来管理引用计数。即要用的时候,retain一次,要释放的时候release一次。直到引用计数为零,系统才会释放对象,回收内存。
ARC是后来才推出的内存管理机制,它简化了流程。引用计数不需要开发人员来管理和维护了,全由系统帮助完成。 简单的来说,ARC无须开发人员考虑内存的管理情况,它会在类对象被引用的时候,引用计数 1;在release的时候,引用计数-1;在类对象不再使用的时候,自动释放其占用的内存。让开发人员从复杂的内存管理中解脱出来,大大地提高了开发人员的效率。
显然地,ARC更好用。那这么好用的ARC,我们再深入讲下它的工作机制。
自动引用计数的工作机制
正如前面所说一样,每当创建一个新的类对象时,ARC就会分配一块内存来储存该对象的信息。内存中会包含对象的类型信息,同时引用计数器会 1 。
当其他对象引用这个类对象时,计数再 1,若一直引用,那引用计数就一直累加。
当引用的类对象设置为nil,内部实现就是调用release一次,引用计数就会-1。多个类对象设置为nil, 就会减少多个计数值。
当对象不再使用了,ARC 就会释放对象所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的对象,不会一直占用内存空间。当然它的判断条件就是引用计数是否为零。为零就释放内存。
当 ARC 释放了类对象,类对象所对应的方法和属性将不能再被调用。否则App就会崩溃。
自动引用计数内部实现机制
在引用计数的底层实现中,是通过维护一个引用计数表来跟踪和计算每一个对象的引用情况。其中表里的key为内存块地址的散列值,value为该对象的引用计数。
每引用一次,value的值就 1, 同样的release一次,value值就-1,只有value值为0,才会销毁该对象并从引用计数表移除该key和value。
上述的对象引用过程,其实就是对对象的强引用。之所以称之为“强”引用,是因为它会将对象牢牢地保持住,只要强引用还在,对象是不允许被销毁的。
在实际开发过程中,声明一个类对象,默认就是强引用。
Person *person1;
该声明等价于
__strong Person *person1;
自动引用计数的实践
下面的例子展示ARC的工作机制。
1.创建一个Person类,定义name属性。在init和dealloc函数上分别打印init和dealloc的日志。
#import <Foundation/Foundation.h>
@interface Person : NSObject {
}
@property(nonatomic,retain) NSString *name;
@end
@implementation Person
- (id)init {
self = [super init];
NSLog(@"Person is being init");
return self;
}
- (void)dealloc {
NSLog(@"Person is being dealloc");
}
@end
2.然后在ViewdidLoad函数中定义 三个对象
Person *person1;
Person *person2;
Person *person3;
由于三个对象没有赋值,所以为空的。
这三个对象是强引用关系,因为它们等价于
__strong Person *person1;
__strong Person *person2;
__strong Person *person3;
3.接着创建Person对象并赋值给person1。
person1 = [[Person alloc] init];
通过这一段代码创建了一个Person对象,并把对象赋值给person1。也就是说person1强引用了Person对象。
在引用计数表中,就实现了 1操作。
4.接着在把person1赋值给person2,person3。实现person2,person3的强引用。
person2 = person1;
person3 = person1;
注:在实际开发中不会这么分解写代码。这里只是方便解说。正常的写法是2,3,4一起完成,如下代码所示:
Person *person1 = [[Person alloc] init];
Person *person2 = person1;
Person *person3 = person1;
5.对person1和person2对象进行释放操作。
person1 = nil;
person2 = nil;
通过这两行代码操作,Person对象内存释放吗? 显然没有。因为person3还持有该引用。
6.最后,person3设置为nil, 引用计数为0,ARC 才会销毁Person对象,回收内存。
以上就是自动引用计数的使用过程。
类对象间的循环引用
在上面的例子中,ARC 会跟踪新创建的Person对象的引用计数,并且会在 Person对象不再被需要时销毁它。
然而,我们可能会写出一个类对象的引用计数永远不能为0 的代码。即如果两个对象互相持有对方,每个对象都让对方一直存在,这种情况就是所谓的循环引用。
下面以具体类来说明下。
1.新建两个类,Person和Car
Class Car;
@interface Person : NSObject {
}
@property(nonatomic,retain) NSString *name;
@property(nonatomic,retain) Car *car;
@end
Class Person;
@interface Car : NSObject {
}
@property(nonatomic,retain) NSString *color;
@property(nonatomic,retain) Person *person;
@end
上述Person类中有两个属性,name表示“Person”对应的姓名,car表示“Person”对应的一辆车。假设这“Person”对象只有姓名和车。
同样地,Car对象下,color和person属性,对应的就是车的款式(以颜色代替)和车主。
2.创建类对应的实例并给属性赋值。
Person * john = [[Person alloc] init];
Car * johnCar = [[Car alloc] init];
john.name = “John”;
john.car = johnCar;
johnCar.color = “red”;
johnCar.person = john;
以上代码,在两个对象被创建和赋值后,就表现了强引用的关系。对象john现在有一个指向Car对象的强引用(johnCar),而变对象johnCar 有一个指向 Person 对象的强引用(car)。如下图。
同样地,这种强引用关系就包含了循环引用。即john对象持有了johnCar,johnCar也持有了john。 如下图。
当我们要释放john 和johnCar时,引用计数并不会降为 0,对象也不会被 ARC 销毁,这样就导致了内存泄露。
john = nil;
johnCar = nil;
未释放对象。
解决类对象的循环引用
针对上面的问题,在OC中,主要使用弱引用来解决循环引用问题。
弱引用
弱引用不会对其引用的对象进行强引用,因而不会阻止 ARC 销毁被引用的对象。
这个特性阻止了引用变为循环引用。我们只要在声明属性或者变量时,在前面加上 weak 关键字,就表明这是一个弱引用。
在底层的实现上,系统维护着一个弱引用表,每一次声明弱应用变量或属性都会在这张表中登记, 当对弱引用变量赋值时,就在这张表中建立起弱引用与对象之间的关系。有多少次赋值,就会建立多少次弱引用关系。
由于弱引用不会持有所引用的对象,即使引用存在,对象也有可能被销毁。因此,ARC 会在引用的对象被销毁后自动将其弱引用赋值为 nil,这个操作是为了对象的安全。
这样,对于上述的john和johnCar的循环引用问题,我们只要一方使用弱引用,就可以解除循环引用的问题。如下图所示:
在person释放的时候,可以不用等待Car的持有关系,因为它是弱引用。这种方式就解决了循环引用的问题,避免了内存泄露。
最后说明下:
关于循环引用有:1.类对象的循环引用;2.委托的循环引用;3.block的循环引用三个场景。
这次主要介绍类对象的循环引用和解决方法。
对于委托的循环引用,根本原因和类对象一样的(相互持有),解决方法就是在声明处使用weak关键字就可以了。
对于block的循环引用,内容比较多,见我的另一文章【探究Block底层原理(三)】