目录
文章目录
1、引用计数
GC一直是各种语言中的重点,因为只有做好了GC才可以最大程度减少内存泄漏,去避免OOM。在Java中引用计数法也是存在的。但是引用计数无法解决类内循环引用的问题,所以在Java回收算法中,更多的是使用可达性分析法,现在来看看OC是怎么解决这个问题的。
1.1 优缺点
OC中采用引用计数的机制,对象的引用计数器会因被引用而加1,被释放而减1。当引用计数为0时,便将该内存收回。说直白一点,就是当使用alloc
、new
方法或者copy消息(接收到消息的对象会创建一个自身的副本)创建一个对象时,对象的保留计数器被设置为1。调用retain
会加1,调用release
就会减1,引用计数清零或者调用dealloc
就销毁。
优点:简单,后期的使用也会因内存的合理分配而流畅
缺点:存在无法回收循环引用的存储对象的缺陷
1.2 alloc
上面我们提到当使用alloc
、new
方法或者copy消息(接收到消息的对象会创建一个自身的副本)创建一个对象时,对象的保留计数器被设置为1。但是alloc
的流程中并没有对引用计数操作的流程,那么为什么新建对象时就会被设为1呢?这时候我去翻看了retainCount
源码
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc; //请注意这里,引用计数的总值是extra_rc的取值和散列表中引用计数表的取值外加1
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
看完源码可以发现,新建的对象引用计数确实为0,那个1不过是多加的。
1.3 引用计数的流程
这边我们通过一个小demo来看一看引用计数器的使用方法,为了更直观的观看对象从初始化到销毁的过程,我新建了一个RetainTracker的类并重写了init和dealloc方法。
- (id) init
{ if (self = [super init]) {
NSLog(@"init count:%d",[self retainCount] );
}
}
- (void) dealloc
{ NSLog(@"bye ");
[super dealloc];
}
RetainTracker *tracker = [[RetainTracker alloc] init];
NSLog(@"%d", [tracker retainCount]);
[tracker retain];
NSLog(@"%d", [tracker retainCount]);
[tracker release];
[tracker release];
NSLog(@"%d", [tracker retainCount]);
打印结果为
init count:1
2
1
bye
2、MRC
MRC指的是手动内存管理,在开发过程中需要开发者手动去编写内存管理的代码。
2.1 持有调用者自己的对象
使用alloc/new/copy/mutableCopy创建返回的对象归调用者所有
NSMutableArray *array = [[NSMutableArray alloc] init];/*NSMutableArray类对象A*/
NSLog(@"%p", array);
[array release]; //释放
NSLog(@"%p", array); //此时再次访问就会报错
当调用release后,释放了其对对象A的引用,计数器减1。对象A此时引用计数值为零,所以对象A被回收。不能访问已经被回收的对象,会发生崩溃。
2.2 持有非调用者拥有的对象
当持有非调用者自己拥有的对象的时候
id obj = [Person person];
[obj retain];
/*do something*/
[obj release];
此时obj变量获得但不持有Person类对象,可以通过retain进行持有该对象。当我们使用完该对象,应该调用release方法释放该对象。
2.3 autorelease pool
前面我们讨论了对象释放的方法,但是什么时间去释放是很难把握的,我们没办法确定什么时候不再使用某个对象。这时我们引入一个自动释放池的概念,首先我们看下autorelease和release的区别:release是马上释放对某个对象的强引用;autorelease是延迟释放某个对象的生命周期。
这个概念其实很好理解,其实就是预先设定了一条会在未来某个时间发送的release消息,其返回值时接受这条消息的对象。当给一个对象发送autorelease消息时,其实就是将该对象添加到了自动释放池中。当自动释放池销毁时,会向池中所有对象发送release消息。
3、ARC
ARC,就像是你有了一位负责内存管理的管家。你只需要像平常那样按需分配并使用对象,编译器就会帮你自动插入retain语句和release语句,无需自己动手。
ARC与垃圾回收器不同。垃圾回收器会定期检查变量和对象,并且跟踪它们的指针。当发现没有任何变量指向某一个对象时,就将该对象视为应该丢弃的垃圾。这也就意味着,垃圾回收器是在运行时工作的,而ARC是在编译阶段工作的。
3.1__strong修饰符
_strong修饰符是id类型和对象类型默认的所有权修饰符。__strong修饰符表示对对象的「强引用」。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
在ARC中,并不是直接执行retain和autorelease操作的,而是通过以下两个方法:
objc_autoreleaseReturnValue(obj);//对应autorelease
objc_retainAutoreleasedReturnValue(obj);//对应retain
然后我们引入一个简单的demo来看下如何实现的:
+ (id)person {
Person *tmp = [[Person alloc] init];//引用计数为1
return objc_autoreleaseReturnValue(id tmp);
}
{
//ARC
_p = [Person person];//_p是强引用属性对应的实例变量
//实现展示
Person *p = [Person person];//Person类对象引用计数为1
_p = objc_retainAutoreleasedReturnValue(p);//递增为1
[_p release];//递减为0
}
以下为两个方法对应的伪代码:
id objc_autoreleaseReturnValue(id obj) {
if ("返回对象obj后面的那段代码是否执行retain") {
//是
set_flag(obj);//设置标志位
return obj;
} else {
return [obj autorelease];
}
}
id objc_retainAutoreleasedReturnValue(obj) {
if (get_flag(obj)) {
//有标志位
return obj;
} else {
return [obj retain];
}
}
从上述示例代码可以看出:
- 当我们用变量p获取person方法返回的对象前,person方法内部会执行objc_autoreleaseReturnValue方法。该方法会检测返回对象之后即将执行的那段代码,如果那段代码要向所返回的对象执行retain方法,则为该对象设置一个全局数据结构中的标志位,并把对象直接返回。反之,在返回之前把该对象注册到自动释放池。
- 当我们对Person类对象执行retain操作的时候,会执行objc_retainAutoreleasedReturnValue方法。该方法会检测对应的对象是否已经设置过标志位,如果是,则直接把该对象返回;反之,会向该对象执行一次retain操作再返回。
3.2 _weak修饰符
之前我们在引用计数那里提到循环引用的问题,那么,__weak就通过对某个对象具有弱引用来解决我们开发中遇到的循环引用问题。因为weak只通过弱引用来引用某个对象,并不会真正意义上的持有该对象。所以我们只需要把上述两个类对象的其中一个属性用weak来修饰,就可以解决循环引用的问题。
例如:
Person __weak *weakPerson = nil;
if (1) {
Person *person = [[Person alloc] init];
weakPerson = person;
NSLog(@"%@", weakPerson);//weakPerson弱引用
}
NSLog(@"%@", weakPerson);
当超出作用域后,person变量对Person对象的强引用失效。Person对象持有者不存在,所以该对象被回收。同时,weakPerson变量对Person的弱引用失效,weakPerson变量被赋值为nil。
输出结果如下:
<Person: 0x600000018120>
(null)
同时,我们还要注意被__weak修饰的变量,不能持有生成的Person类对象,对象创建完立即被释放。
Person __weak *weakPerson = [[Person alloc] init]; //错误的初始化方式
3.3 __unsafe_unretained
一个变量被__unsafe_unretained修饰,那么该变量不属于编辑器的内存管理对象。该修饰符表明不保留值,即对其所指向的对象既不强引用,也不弱引用。iOS4时代的东西了。。。。。。
3.4_ _autoreleasing
在ARC和MRC中,autorelease作用一致,只是两者的表现方式有所不同。
@autoreleasepool {
id __autoreleasing obj=[[NSObject alloc]init];
}
可以理解为,在 ARC 有效时,用@autoreleasepool
替代了 NSAutoreleasepool 类,用附有__autoreleasing 修饰符的变量替代了 autorelease 方法。
4、@property关键字
@property关键字帮我们自动生成getter和setter(声明方法,并实现方法。当然,这部分代码并不会出现在你的项目中,是隐藏起来的)。 写@property
声明属性,其实是做了三件事
1、.h: 声明了getter和setter方法;
2、.h: 声明了实例变量(默认:下划线+属性名);
3、.m: 实现了getter和setter方法。
而使用@property
来声明属性,是要选择括号里的所有特性。attribute主要有三种类型(实际上最多可以写6个特性,后面详述),每种类型都有默认值。如果什么都不写,系统就会取用默认值
4.1 Atomicity(原子性)
原子性最简单的体现就是加锁,加锁是在增加性能耗费的情况下保证线程安全。
- atomic(默认值)
使用atomic,在一定程度上可以保证线程安全,「atomic的作用只是给getter和setter加了个锁」。也就是说,有线程在访问setter,其他线程只能等待完成后才能访问。
- nonatomic
而用nonatomic,则不保证你获得的是有效值,如果像上面所述,读、写两个线程同时访问变量,有可能会给出一个无意义的垃圾值。
4.2 Access(存取特性)
- readwrite(默认值)
同时生成getter和setter
- readonly
只生成getter。
4.3 Storage(内存管理特性)(管理对象的生命周期的)
- strong (默认值)
- weak
- copy
在一个新对象引用计数为1,赋值时对传入值进行一份拷贝,所以才使用copy关键字。你将一个对象赋值给一个属性,该属性并不会持有对象,而是会创建一个新对象,并将这个对象拷贝给它
- assign
主要修饰基本数据类型和C数据类型,不能修饰对象类型,如NSInterger
,CGFloat
等类型
剩余的属性不是常用到,所以略过