本文为学习笔记,主要是以下两篇参考文章内容的简化版,加深理解,复习使用。
参考文章
一、堆(Heap)和栈(stack)的概念
-
栈存放值类型
-
堆存放对象类型,对象的引用计数是在堆内存中操作的
堆是什么
堆是计算机科学中一类特殊数据结构的统称。通常是一个可以被看做一棵树的数组对象。
在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些时间不短,但具有重要作用的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。
堆又被称为优先队列,但是堆并不是队列。在队列中只能按照进入的先后顺序来取出元素,但是在堆中是按照元素的优先级取出元素
栈是什么
栈是计算机科学中一种特殊的串列形式的抽象资料型别。
只允许在链接串列或阵列的一端进出数据(top: 顶端元素,push: 加入元素, pop: 取出元素)。
按照后进先出的原理运作。
栈也可以用一维数组或连结串列的形式来完成。
内存分配
堆栈空间分配
-
栈(操作系统):由操作系统自动分配释放。存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
-
堆(操作系统):一般由程序员分配释放,否则程序结束时可能由OS回收,分配方式类似于链表。
堆栈缓存方式
-
栈使用的一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放。
-
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对慢一些。
二、引用计数
引用计数是一种内存管理技术,指将资源(可以是对象、内存或磁盘空间等)的被引用次数保存起来,当被引用次数变为0时就将其释放的过程。
1.MRC操作对象
NSObject中提供了有关引用计数的如下方法:
-
retain: 引用计数器 +1
-
release: 引用计数器 -1
-
autorelease: 不改变该对象的引用计数器的值,只是将对象添加到自动释放池中
-
retainCount: 返回该对象的引用计数的值
| 对象操作 | Objective-C方法 |
|---|---|
| 生成并持有对象 | alloc/new/copy/mutableCopy等方法 |
| 持有对象 | retain方法 |
| 释放对象 | release方法 |
| 废弃对象 | dealloc方法 |
自己生成的对象,自己持有
id obj = [[NSObject alloc] init];
[obj release]; // 释放对象
非自己生成的对象,自己也能持有
id obj = [NSMutableArray array]; // 非自己生成的对象,暂时没有持有
[obj retain]; // 通过retain持有对象
[obj release]; // 通过release释放对象
autorelease pool
每条线程都包含一个与其对应的自动释放池,当线程被终止的时候,对应的自动释放池会被销毁。同时,处于该自动释放池内的对象将会进行一次release操作
2.ARC操作对象的修饰符
2.1 __strong修饰符
在ARC模式下,id和OC对象的所有权修饰符默认都是__strong。在变量超出作用域后,该变量就会被废弃,同时赋值给该变量的对象也会被释放,例如:
{
// 变量p持有Person对象的强引用
Person *p = [Person person];
// __strong修饰符可以省略
// Person __strong *p = [Person person];
}
// 变量p超出作用域,释放对Person对象的强引用
// Person对象持有者不存在,该对象被释放
上述例子对应的MRC代码如下:
{
Person *p = [Person person];
[p retain];
[p release];
}
strong与属性
如果一个属性的修饰符是strong,对于其实例变量所持有的对象,编译器会在该实例变量所属类的dealloc方法为其添加释放对象的方法。在MRC下,dealloc如下:
- (void)dealloc {
[p release];
[super dealloc];
}
strong的实现
在ARC中,除了会自动调用“保留”和释放方法外,还进行了优化。比如某个对象执行了多次“保留”和释放方法,那么ARC针对特殊情况有可能会将该对象的“保留”和释放成对地移除:
+ (id)person {
Person *tmp = [[Person alloc] init]; // 引用计数1
[tmp autorelease]; // 注册到自动释放池(ARC下无效)
return tmp;
}
{
// ARC代码
_p = [Person person];
// MRC实现展示
Person *p = [Person person]; // Person类对象引用计数为1
_p = [p retain]; // Person类对象引用计数为2
[_p release]; // Person类对象引用计数为1
}
// 清空自动释放池, Person类对象引用计数为0,释放对象
上述代码中,+(id)person方法中的autorelease方法延迟了该对象的生命周期,在稍后自动释放池会进行release操作。
而_p通过retain来持有该对象,使用完立马执行release操作。
可以看出retain和autorelease是多余的。可以简化成如下代码:
+ (id)person {
Person *tmp = [[Person alloc] init]; // 引用计数1
return tmp;
}
{
// ARC代码
_p = [Person person];
// MRC实现展示
_p = [Person person]; // Person类对象引用计数为1
[_p release]; // Person类对象引用计数为0,释放对象
}
那么ARC是如何判断是否移除这种成对的操作呢?其实在ARC中,并不是直接执行retain和autorelease操作的,而是通过以下两个方法:
objc_autoreleaseReturnValue(obj);//对应autorelease
objc_retainAutoreleasedReturnValue(obj);//对应retain
以下为两个方法对应的伪代码:
id objc_autoreleaseReturnValue(id obj) {
if ("返回对象obj后面的那段代码是否执行retain") {
// 是
set_flag(obj); // 设置标志位
return obj;
} else {
return [obj autorelease];
}
}
id objc_retainAutoreleasedReturnValue(id obj) {
if (get_flag(obj)) {
// 有标志位
return obj;
} else {
return [obj retain];
}
}
通过以上两段伪代码,重新梳理代码如下:
+ (id)person {
Person *tmp = [[Person alloc] init]; // 引用计数1
return objc_autoreleaseReturnValue(id tmp);
}
{
// ARC代码
_p = [Person person];
// MRC实现展示
Person *p = [Person person];
_p = objc_retainAutoreleasedReturnValue(p); // Person类对象引用计数为1
[_p release]; // Person类对象引用计数为0,释放对象
}
在ARC中,通过设置和检测标志位可以移除多余的成对(“保留”&“释放”)操作,优化程序的性能。
2.2 __weak修饰符
__weak修饰符一般用于解决开发中遇到的循环引用问题。
基于运行时库,如果变量或属性使用weak来修饰,当其所指向的对象被回收,会自动为该变量或属性赋值为nil,有效地避免野指针奔溃。
weak和变量
如果一个变量被__weak修饰,代表该变量对所指向的对象具有弱引用。例如以下代码:
Person __weak weakPerson = nil;
if (1) {
Person *person = [[Person alloc] init];
weakPerson = person;
NSLog(@"%@", weakPerson);// 输出: <Person: 0x600000018120>
}
NSLog(@"%@", weakPerson);// 输出:(null)
从上述输出结果可以看出,当超出作用域后,person变量对Person对象的强引用失效。Person对象持有者不存在,所以该对象被回收。同时,weakPerson变量对Person的弱引用失效,weakPerson变量被赋值为nil。
另外需要注意,如果一个变量被weak修饰,那这个变量不能持有对象实例,编译器会发出警告。如下代码:
Person __weak *weakPerson = [[Person alloc] init];
因为weakPerson被__weak修饰,不能持有生成的Person对象。所以Person对象创建完立即被释放,编译器会给出相应的警告:
Assigning retained object to weak variable; object will be released after assignment
weak的实现
// ARC下
Person *person = [[Person alloc] init];
Person __weak *p = person;
// MRC对应的模拟代码
Person *person = [[Person alloc] init];
Person *p;
objc_initWeak(&p, person);
objc_destroyWeak(&p, 0);
上述MRC对应的模拟代码用objc_initWeak和objc_destroyWeak函数对p进行初始化和释放。这两个函数都调用同一个函数。如下:
id p;
p = 0;
objc_storeWeak(&p, person);
objc_storeWeak(&p, 0);
根据上述代码分析:
1.初始化变量
使用变量弱引用指向一个对象时,通过传入变量的地址和赋值对象两个参数来调用objc_store方法。该方法内部会将对象的地址(&person)作为键值,把变量p的地址(&p)注册到weak表中。
2.释放变量
调用objc_storeWeak(&p,0)把变量的地址从weak表中删除。变量地址从weak表删除前,利用被回收对象的地址作为键值进行检索,把对应的变量地址赋值为nil。
weak和访问
访问被__weak修饰过的变量所指向的对象时,对应的模拟代码如下:
Person *person = [[Person alloc] init];
Person __weak *p = person;
NSLog(@"%@",p);
// MRC下模拟代码
Person *person = [[Person alloc] init];
Person *p;
objc_initWeak(&p, person);
id tmp = objc_loadWeakRetained(&p);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&p);
通过上述代码可以看出,访问一个被__weak修饰的变量,相对于赋值多了两个步骤:
-
objc_loadWeakRetained, 通过该函数去除对应变量锁引用的对象并retain;
-
把变量指向的对象注册到自动释放池。在自动释放池结束前都能安全使用该对象。
需要注意的是,如果大量访问weak变量,会导致变量所引用的对象被多次加入自动释放池,从而影响性能。如果需要大量访问,可以通过__strong修饰的变量来解决,如下:
Person __weak *p = obj;
Person *tmp = p; // p引用的对象被注册到自动释放池
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
通过利用__strong中间变量的使用,p引用的对象仅注册 1 次到自动释放池中,有效地减少了自动释放池中的对象。
2.3 __unsafe_unretained
如果一个变量被__unsafe_unretained修饰,那么该变量不属于编辑器的内存管理对象。该修饰符表明不保留值,即对其所指向的对象既不强引用,也不弱引用。例如以下代码:
Person __unsafe_unretained unretainedPerson;
if (1) {
Person *p = [[Person alloc] init];
unretainedPerson = p;
NSLog(@"%@", unretainedPerson);// 输出: <Person: 0x600000018120>
}
NSLog(@"%@", unretainedPerson);// 奔溃
当超出作用域,变量p对Person类对象的强引用失效。Person类对象的引用计数为0,该对象被回收。当再次使用unretainedPerson变量的时候,因为其指向的对象已经被回收,所以会发生野指针奔溃。而使用__weak修饰的话,变量所指向的对象被回收,会将变量赋值为nil。
被__unsafe_unretained修饰的变量跟__weak一样,不能持有对象实例。对象创建完立马被回收,编译器会给出相应警告。
当我们给被__unsafe_unretained修饰的变量赋值时,必须保证赋值对象确实存在,否则程序会发生奔溃。
2.4 __autoreleasing
在ARC和MRC中,autorelease作用一致,只是两者的表现方式有所不同。
1.如果返回的对象归调用者所有,如下:
@autoreleasepool {
Person __autoreleasing *p = [[Person alloc] init];
}
模拟代码如下:
id pool = objc_autoreleasePoolPush();
Person *p = [[Person alloc] init]; // 持有
objc_autorelease(p);
objc_autoreleasePoolPop(pool);
2.如果返回的对象 不 归调用者所有,如下:
@autoreleasepool {
Person __autoreleasing *p = [Person person];
}
模拟代码如下:
id pool = objc_autoreleasePoolPush();
Person *p = [Person person];
objc_retainAutoreleasedReturnValue(p); // 持有(优化后的retain方法)
objc_autorelease(p);
objc_autoreleasePoolPop(pool);
从以上两个例子可以看出,__autoreleasing的实现都是通过objc_autorelease()函数,只不过是持有方法有所不同而已。