———-android培训、Java培训、iOS培训,期待与您交流———-
一、基本概念
1.什么是ARC
ARC(自动引用计数)。在ARC模式下,系统会自动追踪对象,并决定判断哪个对象仍会使用哪个对象不会被使用,并在适当的位置插入retain和release。
ARC并非垃圾回收机制,而只是一个编译器特性,即在需要retain和release的地方自动加入该语句。这一工作是在编译时完成的,相比较而言垃圾回收是在程序运行时工作。
2.ARC的判断准则
系统如何判断一个对象是否应该被释放?
当没有强指针指向一个对象时,该对象就会被释放
3.ARC下指针的分类:
- 强指针:默认情况下,所有的指针都是强指针,关键字__strong。
- 弱指针:__weak关键字修饰的指针。例如声明一个弱指针:__weak Person *p。
4.注意事项
ARC是一种机制,通过是否有强指针指向对象来判断是否需要将该对象销毁。因此在ARC模式下,不能使用release、autorelease、retain以及retainCount。与MRC一样,ARC管理的是NSObject的子类对象,不能管理其他基础数据类型(例如:整型、结构体等)。
二、ARC的基本使用
编译器默认状态下就是开启ARC机制的。使用ARC时,不需要额外的任何操作来管理对象,对象会在没有强指针指向时自动释放。下例中的对象已经重写了dealloc方法:
ARC下重写dealloc方法不需要写[super dealloc]语句
int main(){
@autoreleasepool{
Person *p = [Person new];
}//->对象已经被释放
return 0;
}
由于在内层的花括号结束时,作为局部变量的指针p会被释放,因此对象Person就没有强指针指向了,于是系统自动调用dealloc方法,销毁该对象。
如果原本指向一个对象的指针指向了其他对象,那么远来的对象也会由于没有强指针指向而被销毁:
int main(){
@autoreleasepool{
Person *p = [Person new];
p = nil;//->对象已经被释放
}
return 0;
}
只有有强指针指向的对象才不会被销毁,而若指针不能保留对象:
int main(){
@autoreleasepool{
Person *p = [Person new];
__weak Person *p2 = p;
p = nil;//->对象已经被释放
}
return 0;
}
三、作为成员变量的对象的ARC
在生成成员变量的set/get方法时,使用@property的参数可以设置该成员变量指针类型(__weak__strong)。设置下面两个对象Person和Dog:
#import "Dog.h"
@implementation Dog
- (void)dealloc
{
NSLog(@"Dog已经被销毁");
}
@end
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Person : NSObject
@property (nonatomic,weak) Dog *dog;
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person已经被销毁");
}
@end
main函数中有如下操作:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
Dog *d = [Dog new];
p.dog = d;
d = nil;//->Dog已经被销毁
p = nil;//->Person已经被销毁
}
return 0;
}
虽然在d指向nil之后p.dog仍然指向Dog对象,但是由于p.dog是弱指针,所以对象在此时已经被销毁。
如果将@property的参数改为(nonatomic,strong)或者(nonatomic),则main函数中必须如下操作才能销毁Dog对象:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
Dog *d = [Dog new];
p.dog = d;
p.dog = nil;
d = nil;//->Dog已经被销毁
p = nil;//->Person已经被销毁
}
return 0;
}
四、循环引用的问题
当两个类相互引用,使用#import编译器就会报错,此时可以使用@class。
使用@class引入一个类时,编译器不会查看该类的具体内容,而只是在引入该类的文件中单纯的知道引入了一个类,因此不会出现两个类相互引用的冲突。例如在Dog类中要引入Person,Person类中要引入Dog,则只需要把本来在.h文件中引入头文件的语句#import “Person.h”和#import “Dog.h”改为@class Person;和@class Dog;即可。
这样使用@class会产生一个副作用。当通过@class引入一个类时,并不能直接调用这个类的方法和成员变量,因为与#import不同,@class只是告诉编译器有一个类,但是并不会将类的具体内容在编译之前进行引入。解决这个问题的方法是在.m文件中通过#import引入类的.h文件。举例如下:
/******Person.h*****/
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property Dog *dog;
-(void)hitDog;
@end
/******Person.m*****/
#import "Person.h"
#import "Dog.h"
@implementation Person
-(void)hitDog{
[_dog bark];
}
- (void)dealloc
{
_dog = nil;
NSLog(@"Person已经被销毁");
}
@end
/******Dog.h*****/
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
@property Person *person;
-(void)bark;
@end
/******Dog.m*****/
#import "Dog.h"
@implementation Dog
-(void)bark{
NSLog(@"汪汪汪!");
}
- (void)dealloc
{
NSLog(@"Dog已经被销毁");
}
@end
上述代码通过使用@class解决了循环引用编译器报错的问题,同时在.m文件中用#import引入类的.h文件,使得类的方法可以在引入该类的类中使用。
但是新的问题又出现了,如果在main函数中执行如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
Dog *d = [Dog new];
p.dog = d;
d.owner = p;
[p.dog bark];
}
return 0;
}
程序执行完内层花括号后并没有自动调用Dog和Person的dealloc方法。当程序执行到内层花括号结束部分时,首先会销毁局部变量p和d,此时p.dog依然作为强指针指向Dog对象,d.owner依然作为强指针指向Person对象。因为两个对象都有强指针指向,所以不会被销毁,这样就会造成内存泄漏。
解决方法有两个:
- 在两个类相互引用时,将其中一个设置为弱指针,即在@property参数中设置为weak。
- 在代码块结束前,手动将相互引用的对象的其中一个成员变量指向nil。
五、ARC项目兼容MRC
要让ARC项目兼容MRC对象,只需要执行如下操作:
在创建target的界面下->Build Phases->Compile Sources->选择MRC文件双击->在Complier Flags中填写-fno-objc-arc