对于现在的Xcode,开发者不需要手动的管理内存和对象的引用计数,自从引入了ARC模式之后,我们不需要过多的关注内存方面,但是对于iOS的对象的引用计数和内存管理的了解是必要的,本篇博客记录ARC的深入学习了解。
ARC(Automatic Reference Counting)是指内存管理中对引用采取自动计数的计数
在Objective-C中采用Automatic Reference Counting(ARC)机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,这就降低程序崩溃、内存泄露等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清除目标对象,并能立刻释放那些不再被使用的对象。如此一来,引用程序具有可预防性,且能流畅运行,速度也将大幅提升。
Buliding Settings
选择找到AutoR这个选项,设置为NO
前言的博客复习了引用计数,需要强调的是ARC的思考方式。如何理解ARC关键就在这。
alloc方法和其他生成并持有的方法存在区别,他属于类方法,其他都是实例方法
既然要探究ARC的本质和深入学习,那么就需要学会查看代码是如何由编译器实现的.
找到对应的二级文件 cd文件地址,输入下面的代码,就出现了main.cpp
方法
clang -rewrite-objc4 main.m
他的内部实现是C++实现的,接下来根据一段代码对ARC进行分析。
void dftFun() {
id obj = [[NSObject alloc] init];
NSLog(@"%@", obj);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
dftFun();
}
return 0;
}
objc_storeStrong
void dftFun() {
id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
void defaultFunction() {
id obj = obj_msgSend(NSObject, @selector(new));
objc_storeStrong(obj, null);
}
obj_msgSend
主要用于消息发送,就是告诉编译器我们要初始化这个对象obj_msgSend(NSObject, @selector(new))
就是新建一个对象,而objc_storeStrong是 objc4
库中的方法,具体逻辑如下:// strong
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
其中涉及到了block里看到的类似 objc_retain
和 objc_release
,其实就是持有和释放对象的过程
objc_retain(obj)
objc_release(prev)
在分析 ARC 相关源码之前,需要对 isa 有一定了解,其中存储了一些非常重要的信息,下面是 isa 的结构组成:
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用优化的isa指针
uintptr_t has_assoc : 1;//->是否包含关联对象
uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
uintptr_t magic : 6;//->固定值,用于判断是否完成初始化
uintptr_t weakly_referenced : 1;//->对象是否被弱引用
uintptr_t deallocating : 1;//->对象是否正在销毁
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
uintptr_t extra_rc : 19; //->存储引用计数
};
};
其中nonpointer、weakly_referenced、has_sidetable_rc
和extra_rc
都是 ARC 有直接关系的成员变量。
objc_object
为什么要引入isa?
因为在OC 中每一个对象都是一个结构体,结构体都包含了一个isa 成员变量。
在运行时,类的对象被定义为objc_object 类型,就是对象结构体
objc_object
就是isa结构体封装而成的
struct objc_object {
isa_t isa;
};
从下面代码可以知道,objc_object
就是 isa 基础上一层封装。
struct objc_class : objc_object {
isa_t isa; // 继承自 objc_object
Class superclass;
cache_t cache; // 方法实现缓存和 vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
Objc_object
的子类, 所以OC的类Objc_Class
的结构和Objc_object
类似。isa:objc_object
指向类,objc_class
指向元类。superclass
:指向父类。cache
:存储用户消息转发优化的方法缓存和 vtable 。这个在小蓝书有了解过,是可以利用缓存优化程序的执行速度和内存占用机制bits
:class_rw_t
和 class_ro_t
,保存了方法、协议、属性等列表和一些标志位。上面的介绍是对ARC的基础理解,了解了每一个对象结构体的基本组成,isa_t
结构体的引入为了下文的探究作揖准备。
OC编程为了处理对象,可将变量类型定义为包含id类型在内的各种类型
对象类型:就是指向NSObject这样的OC类的指针,NSObject *
, id类型则可以隐藏对象类型的类名部分, 和void *
一样
ARC有效的时候,id必须附加所有权修饰符,一共四种
_strong修饰符是id类型和对象类型默认的所有权修饰符
在 MRC 时代 Retain 修饰符将会使被引用的对象引用计数 + 1 ,在 ARC 中 __strong
修饰符作为其替代者,不论调用哪种方法,强引用修饰的变量会持有该对象,如果已经持有则引用计数不会增加。
__strong修饰符是id类型和对象类型默认添加的修饰符,如下的代码
id obj = [NSObject new];
等同于
id __strong obj1 = [NSObject new];
强引用对象的所有者和对象的生命周期
上面讲的是自己生成并持有的对象的生命周期,那么非自己生成但是持有的对象声明周期如何呢
通过__strong变量间的互相引用理解废弃和引用
id __strong obj0 = [[NSObject alloc] init];//生成对象A
id __strong obj1 = [[NSObject alloc] init];//生成对象B
id __strong obj2 = nil;
obj0 = obj1;//obj0强引用对象B;而对象A不再被ojb0引用,被废弃
obj2 = obj0;//obj2强引用对象B(现在obj0,ojb1,obj2都强引用对象B)
obj1 = nil;//obj1不再强引用对象B
obj0 = nil;//obj0不再强引用对象
obj2 = nil;//obj2不再强引用对象B,不再有任何强引用引用对象B,对象B被废弃
@interface TestClass : NSObject {
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
#import "TestClass.h"
@implementation TestClass
- (id)init {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj;
}
@end
id test0 = [[TestClass alloc] init];//生成TestA
id test1 = [[TestClass alloc] init];//生成TestB
通过set方法给 两个对象的成员变量分别赋值另一个对象所持有的TestA/TestB对象
[test0 setObject:test1];
[test1 setObject:test0];
这时候发现如果想要尝试废弃testa, 需要test0 和test1.obj置空。因为成员变量的生命周期是与对象同步的 然而废除test1.obj需要废除test1,也就是废除testb如此一来重复上述操作,循环引用,也就是强引用容易造成循环引用
id t1 = [[TestClass alloc] init];
[t1 setObject:t1];
循环引用容易发生内存泄漏,因为对象在超出生命周期之后应该被废弃,但是还是继续存在了。
避免循环引用出现了__weak修饰符,他也是四大所有权修饰符之一。
__weak 弱引用 弱引用不能持有对象实例。
id __weak obj = [NSObject new];
将保留对象分配给弱变量,因为不自己持有对象,对象将在分配后释放.
可以将对象赋值给__strong修饰的变量之后再次赋值给__weak修饰符变量即可
id __strong obj1 = [NSObject new];
id __weak obj = obj1;
也就是obj变量成了持有对象的弱引用,达到了效果
同理,可以在刚才的循环引用部分加入__weak修饰符修饰使变量在超出生存区域的时候被释放
__weak
在持有某对象的弱引用时候,该对象被废弃,该弱引用将自动失效且处于nil被赋值状态,叫做空弱引用
id __weak obj = nil;
{
id __strong obj1 = [NSObject new];
obj = obj1;
NSLog(@"%@", obj);
}
NSLog(@"%@", obj);
废弃对象的同时,持有该弱引用的obj1变量的弱引用失效,nil赋值给obj1.
__unsafe_unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。
附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。
和__weak一样,不能持有自己直接生成的对象,所以生成的对象也会被自己释放。
同样的代码,换成 __unsafe_unretained
id __unsafe_unretained obj2 = nil;
{
id __strong obj1 = [NSObject new];
obj2 = obj1;
NSLog(@"1%@", obj2);
}
NSLog(@"2%@", obj2);![请添加图片描述](https://img-blog.csdnimg.cn/491c31bb07884096ba9273881d637e15.png)
代码直接崩溃了,去掉最后一行
id __unsafe_unretained obj2 = nil;
{
id __strong obj1 = [NSObject new];
obj2 = obj1;
NSLog(@"1%@", obj2);
}
NSLog(@"2%@", obj2)
之后代码崩溃,结果obj2指向了指针存在,指向了不存在的对象。weak
修饰的指针变量,在指向的内存地址销毁后自动置为 nil。_Unsafe_Unretain
不会置为 nil,容易出现 悬垂指针,发生崩溃。但是 _Unsafe_Unretain
比 __weak
效率高。在使用__unsafe_unretained
修饰符时,赋值给附有__strong
修饰符的变量时有必要确保被赋值的对象确实存在,如果不存在,那么程序就会崩溃
@autorelease
块代替NSAutoreleasPool
类,附有__autoreleasing
的修饰符变量代替autorelease
方法。编译器会检查方法名是否以alloc/new/copy/mutableCopy
开始,如果不是讲自动将返回值的对象注册到autoreleasepool中
和__strong一样,下列情况不使用__autoreleasing也自动把对象注册到自动释放池里
+ (id) array {
return [[NSMutableArray alloc]init];
}
如下:
+ (id) array {
id obj = [[NSMutableArray alloc]init];
return obj;
}
return使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为函数的返回值,编译器也会自动注册到自动释放池
在访问附有__weak修饰符的变量的时候,实际上必定要访问到autoreleasepool对象,__weak是对于某个对象的弱引用,而对象有可能在超出自己的作用域的时候被废弃了,所以访问到autoreleasepool为了保证对象存在到我们需要的时候
显式的使用__autoreleaseing修饰符的时候,对象必须要为自动变量(局部变量,函数,方法参数。
不论ARC有效或者无效,都推荐使用pool上的对象,因为Runloop等实现不论ARC有效还是无效,均能随时释放注册到pool中的对象
ARC需要遵守如下规则
前面两条就是之前所了解过
ARC无效的时候,用于对象生成的持有的方法必须遵守以下命名规则
alloc new copy mutableCopy等上述名称方法返回对象必须返回给调用方应当持有的对象,ARC有效和上述一样,但是init需要注意
以init开始的方法规则要比上述严格,该方法必须是实例方法并且必须返回对象,返回的类型为方法的声明类型,超类或子类,该方法返回对象不注册到autoPool里。
- (id) initWithObject:(id) obj;上述方法遵守规则并返回了对象
- (void) initThisObject;没有返回对象不允许使用
- (void) initialize 之前说过,这个方法不属于上述行列,是在对象初始化之前必须调用的,不需要遵守上述规则
dealloc无法释放不属于该对象的一些东西,需要我们重写时加上去,例如
通知的观察者,或KVO的观察者
对象强委托/引用的解除(例如XMPPMannerger的delegateQueue)
做一些其他的注销之类的操作(关闭程序运行期间没有关闭的资源)
ARC无效的时候调用需要[super dealloc]
ARC有效的时候我们需要记述废弃对象时候我们需要的处理
ARC无效的时候我们可以转化“id”和“void*”和调用一些实例方法
id obj = [[NSObject alloc] init];
void *p = obj;
ARC有效的时候这样是不行的
在ARC有效的时候 id 型或对象型变量赋值给void * 或者逆向赋值时都需要进行特定的转换。如果只想单纯地赋值,则可以使用 “__bridge转换”。
- (void)OCAndVoidUse__bridgeInARC {
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)(p);
}
但是转换为 void * 的 __bridge转换,安全性与赋值给 __unsafe_unretained
修饰符相近,甚至会更低 。如果管理时不注意赋值对象的所有者,就会 因悬垂指针而导致程序崩溃 。
此时P不持有对象 __bridge
并不会改变持有情况。
这里简单介绍一下属性声明表
ARC有效的时候,属性声明使用的属性来用,代替作用
copy方法是赋值给copy with zone方法的复制出来的的对象,需要注意。
ARC的规则是针对MRC和ARC下不同的总结,更多的是表面的方法需要注意的东西,ARC的实现则是探讨到了底层的代码,需要更加详细的学习,二者结合更能理解ARC,先学会表面方法的的规则,在去深入学习