内存中的五大区域
引用计数
ARC MRC
属性所有权
僵尸对象、野指针、空指针分别指什么,有什么区别?
内存中的五大区域
内存分为5个区域,分别指的是----->栈区/堆区/BSS段/数据段/代码段
栈:存储局部变量,当其作用域执行完毕之后,就会被系统立即收回
堆:存储OC对象,手动申请的字节空间,需要调用free来释放
BSS段:未初始化的全局变量和静态变量,一旦初始化就会从BSS段中回收掉,转存到数据段中
数据段:存储已经初始化的全局变量和静态变量,以及常量数据,直到结束程序时才会被立即收回
代码段:代码,直到结束程序时才会被立即收回
引用计数
引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。
Snip20190321_13.png
内存管理的思考方式
- 自己生成的对象,自己持有
- 不是自己生成的对象,自己也能持有
- 不再需要自己持有的对象时就释放
- 非自己持有的对象无法释放
自己生成的对象,自己持有
关键字 alloc
,new
,copy
,mutableCopy
/*
* 自己生成并持有该对象
*/
id obj0 = [[NSObeject alloc] init];
id obj1 = [NSObeject new];
不是自己生成的对象,自己也能持有
/*
* 持有非自己生成的对象
*/
id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
[obj retain]; // 自己持有对象
不再需要自己持有的对象时就释放
/*
* 不在需要自己持有的对象的时候,释放
*/
id obj = [[NSObeject alloc] init]; // 此时持有对象
[obj release]; // 释放对象
/*
* 指向对象的指针仍就被保留在obj这个变量中
* 但对象已经释放,不可访问
*/
非自己持有的对象无法释放
/*
* 非自己持有的对象无法释放
*/
id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
[obj release]; // ~~~此时将运行时crash 或编译器报error~~~ 非 ARC 下,调用该方法会导致编译器报 issues。此操作的行为是未定义的,可能会导致运行时 crash 或者其它未知行为
非自己生成的对象,且该对象存在,但自己不持有
- (id) getAObjNotRetain {
id obj = [[NSObject alloc] init]; // 自己持有对象
[obj autorelease]; // 取得的对象存在,但自己不持有该对象
return obj;
}
我们使用了autorelease方法,用该方法,可以使取得的对象存在,但自己不持有
像
[NSMutableArray array]
[NSArray array]
都可以取得谁都不持有的对象,这些方法都是通过autorelease实现的。
ARC
自动的引用计数(Automatic Reference Count 简称 ARC),是苹果在 WWDC 2011 年大会上提出的用于内存管理的技术,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次自己写retain,release代码,降低了程序崩溃,内存泄漏风险,同时也减少开发者的工作量。
所有权限修饰符
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
说到变量所有权修饰符,有人可能会跟属性修饰符搞混,这里做一个对照关系小结:
-
assign 对应的所有权类型是 __unsafe_unretained。
-
copy 对应的所有权类型是 __strong。
-
retain 对应的所有权类型是 __strong。
-
strong 对应的所有权类型是 __strong。
-
unsafe_unretained对应的所有权类型是__unsafe_unretained。
-
weak 对应的所有权类型是 __weak。
除了weak这些修饰符之外,这些修饰符在非ARC模式下可用。
__strong
__strong
表示强引用,对应定义 property
时用到的 strong。当对象没有任何一个强引用指向它时,它才会被释放。如果在声明引用时不加修饰符,那么引用将默认是强引用。当需要释放强引用指向的对象时,需要保证所有指向对象强引用置为 nil。__strong
修饰符是id
类型和对象类型默认的所有权修饰符。
{
id __strong obj = [[NSObject alloc] init];
}
//编译器的模拟代码
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
// 出作用域的时候调用
objc_release(obj);
虽然ARC有效时不能使用release方法,但由此可知编译器自动插入了release。
对象是通过除alloc、new、copy、multyCopy外方法产生的情况
{
id __strong obj = [NSMutableArray array];
}
//编译器的模拟代码
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue
函数主要用于优化程序的运行。它是用于持有(retain)对象的函数,它持有的对象应为返回注册在autoreleasePool中对象的方法,或是函数的返回值。像该源码这样,在调用array类方法之后,由编译器插入该函数。
而这种objc_retainAutoreleasedReturnValue
函数是成对存在的,与之对应的函数是objc_autoreleaseReturnValue
。它用于array类方法返回对象的实现上。下面看看NSMutableArray
类的array方法通过编译器进行了怎样的转换:
+ (id)array
{
return [[NSMutableArray alloc] init];
}
//编译器模拟代码
+ (id)array
{
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_msgSend(obj,@selector(init));
// 代替我们调用了autorelease方法
return objc_autoreleaseReturnValue(obj);
}
我们可以看见调用了objc_autoreleaseReturnValue
函数且这个函数会返回注册到自动释放池的对象,但是,这个函数有个特点,它会查看调用方的命令执行列表,如果发现接下来会调用objc_retainAutoreleasedReturnValue
则不会将返回的对象注册到autoreleasePool
中而仅仅返回一个对象。达到了一种最优效果。如下图:
__weak
__weak
表示弱引用,对应定义 property
时用到的 weak
。弱引用不会影响对象的释放,而当对象被释放时,所有指向它的弱引用都会自定被置为 nil,这样可以防止野指针。使用__weak
修饰的变量,即是使用注册到autoreleasePool
中的对象。__weak
最常见的一个作用就是用来避免循环循环。需要注意的是,__weak
修饰符只能用于 iOS5 以上的版本,在 iOS4 及更低的版本中使用 __unsafe_unretained
修饰符来代替。
__weak 的几个使用场景:
- 在 Delegate 关系中防止循环引用。
- 在 Block 中防止循环引用。
- 用来修饰指向由 Interface Builder 创建的控件。比如:@property (weak, nonatomic) IBOutlet UIButton *testButton;。
{
id __weak obj = [[NSObject alloc] init];
}
编译器转换后的代码如下:
id obj;
id tmp = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_initweak(&obj,tmp);
objc_release(tmp);
objc_destroyWeak(&object);
根据我们的知识,可以知道NSObject对象在生成之后立马就会被释放,其主要原因是__weak修饰的指针没有引起对象内部的引用计数器的变化
因此,__weak修饰的指针常用于打破循环引用或者修饰UI控件,关于__weak修饰的指针引用场景这里不叙述,下面主要介绍其原理
原理
我们知道弱指针有两个作用:
一. 修饰的指针不会引起指向的对象的引用计数器变化
二. 当指向的对象被销毁时,弱指针全部置为nil, 那么除了这些之外,我们还有一个要说的就是,为什么我们在程序中不能频繁的使用weak呢?
为什么弱指针不会引起指向的对象的引用计数器发生变化
通过objc_initweak函数初始化含有__weak修饰的变量,在变量作用域结束时通过objc_destroyWeak
对于__weak
内存管理也借助了类似于引用计数表的散列表,它通过对象的内存地址做为key,而对应的__weak修饰符变量的地址作为value注册到weak表中,在上述代码中objc_initweak
就是完成这部分操作,而objc_destroyWeak
则是销毁该对象对应的value。当指向的对象被销毁时,会通过其内存地址,去weak表中查找对应的__weak修饰符变量,将其从weak表中删除。所以,weak在修饰只是让weak表增加了记录没有引起引用计数表的变化。
对象通过objc_release释放对象内存的动作如下:
- objc_release
- 因为引用计数为0所以执行dealloc
- _objc_rootDealloc
- objc_dispose
- objc_destructInstance
- objc_clear_deallocating
而在对象被废弃时最后调用了objc_clear_deallocating
,该函数的动作如下:
- 从weak表中获取已废弃对象内存地址对应的所有记录
- 将已废弃对象内存地址对应的记录中所有以weak修饰的变量都置为nil
- 从weak表删除已废弃对象内存地址对应的记录
- 根据已废弃对象内存地址从引用计数表中找到对应记录删除
据此可以解释为什么对象被销毁时对应的weak指针变量全部都置为nil,同时,也看出来销毁weak步骤较多,如果大量使用weak的话会增加CPU的负荷。
还需要确认一点是:使用__weak修饰符的变量,即是使用注册到autoreleasePool中的对象。
{
id __weak obj1 = obj;
NSLog(@"obj2-%@",obj1);
}
编译器转换上述代码如下:
id obj1;
objc_initweak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&obj1);
objc_loadWeakRetained
函数获取附有__weak
修饰符变量所引用的对象并retain
, objc_autorelease
函数将对象放入autoreleasePool
中,据此当我们访问weak
修饰指针指向的对象时,实际上是访问注册到自动释放池的对象。因此,如果大量使用weak的话,在我们去访问weak
修饰的对象时,会有大量对象注册到自动释放池,这会影响程序的性能。
推荐方案 : 要访问weak修饰的变量时,先将其赋给一个strong变量,然后进行访问
__unsafe_unretained
作用
__unsafe_unretained作用需要和weak进行对比,它也不会引起对象的内部引用计数器的变化,但是,当其指向的对象被销毁时__unsafr_unretained修饰的指针不会置为nil。而且一般__unsafe_unretained就和它的名字一样是不安全,它不纳入ARC的内存管理
__autoreleasing
将对象赋值有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。
@autoeleasepool {
// 如果看了上面__strong的原理,就知道实际上对象已经注册到自动释放池里面了
id __autoreleasing obj = [[NSObject alloc] init];
}
编译器转换上述代码如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
编译器转换上述代码如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
上面两种方式,虽然第二种持有对象的方法从alloc方法变为了objc_retainAutoreleasedReturnValue函数,都是通过objc_autorelease,注册到autoreleasePool中。
僵尸对象、野指针、空指针分别指什么,有什么区别?
僵尸对象:占用空间被释放的对象
野指针:指向僵尸对象的指针(给野指针发消息会报错)
空指针:指向nil的指针(给空指针发消息会不报错)
因为给野指针发消息会报错,因此我们要监听僵尸对象,这样就可以在控制台输出错误原因
设置如下图
image
网友评论