【iOS】—— ARC学习
ARC
文章目录
- ARC
- 内存管理的思考方式
- 自己生成的对象自己持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 无法释放非自己持有的对象
- 所有权修饰符
- 在什么时候会用到weak和strong?
- __unsafe_unretained
- __autoreleasing
- __autoreleasing的应用场景
- ARC规则
- 必须遵守内存管理的方法名规则
- 不要显式调用dealloc
- 显示的转化“id”和“void*”
- __bridge总结
- 属性声明的属性与所有权修饰符的对应关系
ARC(Automatic Reference Counting)是指内存管理中对引用采取自动计数的计数。
在Objective-C中采用Automatic Reference Counting(ARC)机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者release代码,这就降低程序崩溃、内存泄露等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清除目标对象,并能立刻释放那些不再被使用的对象。如此一来,引用程序具有可预防性,且能流畅运行,速度也将大幅提升。
内存管理的思考方式
- 自己生成的对象自己所持有
- 非自己生成的对象自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
对象操作 | Objective-c |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy方法 |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
这些有关Objective-C的内存管理的方法,包含在Cocoa框架中用于OS X,iOS应用开发。Cocoa框架中Foundation框架库的NSObject类负担内存管理的职责。
自己生成的对象自己持有
使用以下名称开头的方法名意味着自己生成的对象只有自己持有
- alloc
- new
- copy
- mutableCopy
//自己生成并持有对象
id obj1 = [[NSObject alloc] init];
id obj2 = [NSObject new];
//自己持有对象
使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋给变量obj。另外,使用new类方法也能生成并持有对象.
copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。与copy方法类似,mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现的。
muatbleCopyWithZone:方法生成并持有对象的副本。两者的区别在于:copy方法生成不可变的对象,而mutableCopy方法生成可变更的对象。这类似于NSArray类对象与NSMutableArray类对象的差异。用这些方法生成的对象,虽然是对象的副本,但同alloc,new方法一样。
非自己生成的对象,自己也能持有
//取得非自己生成并持有的对象
id obj = [NSMutableArray array];
//取得的对象存在,但是自己不持有
[obj retain];
//自己持有对象
通过retain方法,非自己生成的对象跟用alloc/new/copy/mutableCopy方法生成并持有的对象一样,成为了自己持有的。
不再需要自己持有的对象时释放
自己持有的对象,一旦不再需要,持有者有义务释放其对象。使用使用release方法。
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
[obj release];
//释放对象
//指向对象的指针仍然保留在变量obj中,貌似能够访问。
//但是对象一经释放绝对不可访问
如此,用alloc方法由自己生成并持有的对象就通过release方法释放了。自己生成而非自己所持有的对象,若用retain方法变为自己持有,也同样可以用release方法释放
//取得非自己生成并持有的对象
id obj = [NSMutableArray array];
//取得的对象存在,但自己不持有对象
[obj retain];
//自己持有对象
[obj release];
//释放对象
//对象不可再被访问
用alloc/new/copy/mutableCopy方法生成并持有的对象,或者用retain方法持有的对象,一旦不再需要,务必要用release方法进行释放。
如果要用某个方法生成对象,并将其返回给该方法的调用方,那么他的源代码是怎样的?
-(id)allocObject {
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//自己持有对象
return obj;
原封不动地返回alloc方法生成并持有的对象,就能让调用方也持有该对象。
//取得非自己生成并持有的对象
id obj1 = [obj0 allocObject];
//自己持有对象
allocObject名称符合前文的命名规则,因此它与用alloc方法生成并持有对象的情况完全相同,所以使用allocObject方法也就意味着“自己生成并持有对象”
那么,调用[NSMutableArray array]方法使取得的对象存在,但自己不持有该对象,又是如何实现的呢?不能使用以alloc/new/copy/mutableCopy开头的方法名,因此要使用object这个方法名
-(id)object {
id obj = [[NSObject alloc] init];
//自己持有对象
[obj autorelease];
//取得的对象存在,但自己不持有该对象
return obj;
}
使用NSMutableArray类的array类方法等可以取得谁都不持有的对象。这些方法都是通过autorelease而实现的。这些用来取得谁都不持有的对象的方法名不能以alloc/new/copy/mutableCopy开头。
id obj1 = [obj0 object];
//取得的对象存在,但自己不持有对象
也能通过retain方法将autorelease方法取得的对象变为自己持有
id obj1 = [obj0 object];
//取得的对象存在,但自己不持有对象
[obj1 retain];
//自己持有对象
无法释放非自己持有的对象
对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或者是用retain方法持有的对象,由于持有者是自己,所以不需要对象时将其释放。而由此以外得到的对象绝不能释放。如果在应用程序中释放了非自己所持有的对象就会造成崩溃。
//自己生成并持有对象
id obj = [[NSobject alloc] init];
//自己持有对象
[obj release];
//对象已释放
[obj release];
//释放之后再次释放非自己持有的对象
//应用程序崩溃
//崩溃情况:再度风气已经废弃了的对象时崩溃
//访问已经废弃的对象时崩溃
或者是“取得的对象存在,但自己不持有对象”时释放
id obj1 = [obj0 object];
//取得的对象存在,但自己不持有该对象
[obj1 release];
//释放了非自己持有的对象
//导致程序崩溃
所有权修饰符
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
对于weak和strong相关的强弱引用问题,从去年暑假到现在一直有学习,但是最近才能搞的十分明白。
我们以例子来看看:
id a = [[NSObject alloc] init];
id __weak b = a;
id c = a;
a = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
c = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
我们来看看输出结果:
从上面可以看出使用__strong 和__weak的区别,因为strong的对象会使retainCount+1,而weak的并不会。
我们打印引用计数看看就知道了:
id a = [[NSObject alloc] init];
id __weak b = a;
id c = a;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)a));
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
a = nil;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
NSLog(@"%@", b);
NSLog(@"%@", c);
c = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
在c被赋值之后,对象的引用计数为2,当a被滞空后,c的引用计数为1,这时候b依然可以打印出来,当c被滞空后,b就也滞空了。
注意:这里提下weak和MRC时代的assign的区别,两者都可以用于弱引用,但是内存释放后使用weak会将对象置nil,而assign不会,会造成野指针,现在assign一般只用在基础类型。
在什么时候会用到weak和strong?
首先一般情况下都用strong,weak是用来解决循环引用的,如果两对象相互持有,或者相互强引用对方,所以两者永远不能被销毁,必须将其中一方的引用改成__weak的才能打破这种死循环,最常见的就是delegate模式和block情况下使用。
1.当两个不同的对象各有一个强引用指向对方时就造成循环引用,会导致对象无法释放,例如我们常用的delegate, 见图:
@property (nonatomic, weak) id<Delegate> delegate
//MRC用assign
2.block的使用经常会有这个问题,在前几期博客中讲到了这个问题。
block的使用也会照成循环引用,比如当一个对象持有block,而该block又持有该对象时。
[self block:^{
self.number = 1;
}];
解决方法:
__weak typeof(self) weakself = self;
[self block:^{
weakself.number = 1;
}];
注意:只有该对象持有block,而block里的代码块又持有该对象时才需要用到weak。
3.NSTimer 也会照成循环引用,所以使用了NSTimer后,释放资源前要先调用invalidate方法
[Timer invalidate];
Timer = nil;
__unsafe_unretained
__unsafe_unretained和__weak很像,唯一区别就是,__unsafe_unretained变量引用的对象再被销毁以后,不会被自动设置为nil,仍然指向对象销毁前的内存地址。所以它的名字叫做unsafe,此时你再尝试通过变量访问这个对象的属性或方法就会crash。
我们还是以之前weak和strong的例子来看看:(把__weak换成__unsafe_unretained)
id a = [[NSObject alloc] init];
id __unsafe_unretained b = a;
id c = a;
a = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
c = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
程序直接崩溃了:
__autoreleasing
- 表示在autoreleasepool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。
- __autoreleasing可以说跟__strong有点类似,你可以放心地在autoreleasing块结束此前都可以正常地使用这个对象,而不用担心它被销毁。
- 这个对象也会被注册到autoreleasepool里,将autoreleasepool结束时候才会被销毁,而在变量的作用域结束后不会像__strong那样马上被release。
__autoreleasing的应用场景
__autoreleasing在我们实际真实的iOS应用开发的时候,基本上是用不到的,我说的用不到只是针对业务程序员来说。实际上编译器是会用到它的,另外在一些方法声明的时候,你也会看到自动地给你加上的__autoreleasing,即时你没有显式地手敲出来。
__autoreleasing示例:
#import "MyObject.h"
@interface MyObject ()
@property (nonatomic, weak)NSObject *weakObj1;
@property (nonatomic, weak)NSObject *weakObj2;
@end
@implementation MyObject
- (void)test1 {
NSLog(@"--1---%@--%@",self.weakObj1,self.weakObj2);
[self test2];
NSLog(@"--3---%@--%@",self.weakObj1,self.weakObj2);
}
- (void)test2 {
__autoreleasing NSObject *obj1 = [NSObject new];
NSObject *obj2 = [NSObject new];
self.weakObj1 = obj1;
self.weakObj2 = obj2;
NSLog(@"--2---%@--%@",self.weakObj1,self.weakObj2);
}
@end
我们来看看输出结果:
__autoreleasing修饰的变量的生命周期不再受当前函数作用域影响,转而被自动缓存池影响,因此用__autoreleasing修饰变量可延长变量的生命周期。
__autoreleasing 怎么做到的?
添加至自动缓存池的变量会在缓存池结束的时候收到一次 release,但是怎么做到摆脱当前函数作用域影响的?
我们来看这个方法:
- (void)test3 {
__autoreleasing NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
}
我们把它编译成汇编看看:
ARC测试`-[MyObject test3]:
0x100003b20 <+0>: sub sp, sp, #0x40
0x100003b24 <+4>: stp x29, x30, [sp, #0x30]
0x100003b28 <+8>: add x29, sp, #0x30
0x100003b2c <+12>: stur x0, [x29, #-0x8]
0x100003b30 <+16>: stur x1, [x29, #-0x10]
0x100003b34 <+20>: adrp x8, 5
0x100003b38 <+24>: str x8, [sp, #0x8]
-> 0x100003b3c <+28>: ldr x0, [x8, #0x140]
0x100003b40 <+32>: bl 0x100003cf0 ; symbol stub for: objc_alloc_init
0x100003b44 <+36>: bl 0x100003cfc ; symbol stub for: objc_autorelease
0x100003b48 <+40>: ldr x8, [sp, #0x8]
0x100003b4c <+44>: str x0, [sp, #0x18]
0x100003b50 <+48>: ldr x0, [x8, #0x140]
0x100003b54 <+52>: bl 0x100003cf0 ; symbol stub for: objc_alloc_init
0x100003b58 <+56>: mov x8, x0
0x100003b5c <+60>: add x0, sp, #0x10
0x100003b60 <+64>: str x8, [sp, #0x10]
0x100003b64 <+68>: mov x1, #0x0
0x100003b68 <+72>: bl 0x100003d68 ; symbol stub for: objc_storeStrong
0x100003b6c <+76>: ldp x29, x30, [sp, #0x30]
0x100003b70 <+80>: add sp, sp, #0x40
0x100003b74 <+84>: ret
我们再将汇编翻译成伪代码:
- (void)test3 {
// __autoreleasing NSObject *obj1 = [NSObject new]; 等同于:
id obj1 = objc_msgSend(NSObject, @selector(new));
objc_autorelease(obj1);
// NSObject *obj2 = [NSObject new]; 等同于:
id obj2 = objc_msgSend(NSObject, @selector(new));
defer {
objc_storeStrong(&obj2,nil)
}
}
我们来分析一下:
- 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,其实就是持有和释放对象的过程
- 检查输入的 obj 地址 和指针指向的地址是否相同。
- 持有对象,引用计数 + 1 。objc_retain(obj)
- 指针指向 obj。
- 原来指向的对象引用计数 - 1。objc_release(prev)
其中 objc_autorelease(obj1) 即是将obj1添加至自动缓存池。
obj1 在被__autoreleasing修饰后被加入至自动缓存池,并且在函数结束时不会再调用objc_storeStrong,因此摆脱函数作用域对它的生命周期的影响。
ARC规则
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必须遵守内存管理的方法名规则
- 不要显式调用dealloc
- 使用@autorelease块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体的成员
- 显式转换id和void*
必须遵守内存管理的方法名规则
ARC无效的时候,用于对象生成的持有的方法必须遵守以下命名规则
alloc new copy mutableCopy等上述名称方法返回对象必须返回给调用方应当持有的对象,ARC有效和上述一样,但是init需要注意
以init开始的方法规则要比上述严格,该方法必须是实例方法并且必须返回对象,返回的类型为方法的声明类型,超类或子类,该方法返回对象不注册到autoPool里。
- (id) initWithObject:(id) obj;上述方法遵守规则并返回了对象
- (void) initThisObject;没有返回对象不允许使用
- (void) initialize 之前说过,这个方法不属于上述行列,是在对象初始化之前必须调用的,不需要遵守上述规则
不要显式调用dealloc
dealloc无法释放不属于该对象的一些东西,需要我们重写时加上去,例如:
通知的观察者,或KVO的观察者
对象强委托/引用的解除(例如XMPPMannerger的delegateQueue)
做一些其他的注销之类的操作(关闭程序运行期间没有关闭的资源)
ARC无效的时候调用需要[super dealloc]
ARC有效的时候我们需要记述废弃对象时候我们需要的处理
显示的转化“id”和“void*”
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并不会改变持有情况。
__bridge总结
- __bridge可以实现Objective-C与C语言变量 和 Objective-C与Core Foundation对象之间的互相转换
- __bridge不会改变对象的持有状况,既不会retain,也不会release
- __bridge转换需要慎重分析对象的持有情况,稍不注意就会内存泄漏
- __bridge_retained用于将OC变量转换为C语言变量 或 将OC对象转换为Core Foundation对象
- __bridge_retained类似于retain,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后持有该对象
- __bridge_transfer用于将C语言变量转换为OC变量 或 将Core Foundation对象转换为OC对象
- __bridge_transfer类似于release,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放