学习目标
1.【掌握】自动释放池
2.【理解】ARC机制
3.【理解】单个对象的内存管理
4.【理解】多个对象的内存管理
5.【掌握】ARC机制下的循环引用问题
6.【了解】MRC和ARC的兼容
7.【了解】MRC自动转为ARC
8.【掌握】分类Category
一、自动释放池
使用@autoreleasepool关键字声明一个代码块,这个代码块中的对象调用autorelease方法后,在自动释放池被销毁的时候,会统一调用一次这些对象的release方法(注意只是调用对象的release方法,并不是直接释放对象)。这样一来就起到了自动释放的作用,不过同时对象的销毁过程也得到了延迟。
应用场景一:创建对象后,我们无需再手动调用一次对象的release方法。
//Person.h文件 #import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic, copy) NSString *name; //重写构造方法初始化对象的属性 - (instancetype)initWithName:(NSString *)name; @end //Person.m文件 #import "Person.h" @implementation Person //重写构造方法初始化对象的属性 - (instancetype)initWithName:(NSString *)name { self = [super init]; if (self) { self.name = name; } return self; } //重写父类dealloc方法监控对象释放情况 - (void)dealloc { NSLog(@"%@释放了",self.name); [self.name release]; [super dealloc]; } @end //main.m文件 #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { Person *p = [[Person alloc] initWithName:@"狗子"]; //大括号代码自动释放池的范围,只对放在他内部的对象有效,对嵌套的无效 @autoreleasepool { //可以在自动释放池外创建对象,但必须在自动释放池内部调用autorelease [p autorelease]; Person *p1 = [[Person alloc] initWithName:@"一狗子"]; //不调用autorelease,就不会调用release [p1 autorelease]; //autorelease方法返回对象本身,所以可以创建对象的时候调用 Person *p2 = [[[Person alloc] initWithName:@"二狗子"] autorelease]; Person *p3 = [[[Person alloc] initWithName:@"三狗子"] autorelease]; [p3 retain]; // [p4 autorelease];//报错,因为第二次调用对象的release方法的时候对象已经被释放 // //所以,一般我们只调用一次对象的autorelease方法,防止野指针错误 Person *p4 = [[[Person alloc] initWithName:@"四狗子"] autorelease]; //自动释放池可以嵌套,不受外层自动释放池影响 @autoreleasepool { //p5在当层自动释放池销毁的时候调用release Person *p5 = [[[Person alloc] initWithName:@"五狗子"] autorelease]; } } /* 五狗子释放了 四狗子释放了 二狗子释放了 一狗子释放了 狗子释放了 当上面@autorelease代码块执行完之后,5个对象都得到了释放,但是p3对象的三狗子并没有释放。 由于我们调用了一次p3的retain方法让他的引用计数器加1,当自动释放池销毁的时候对6个对象都调用了一次release方法。 但此时p3调用release后引用计数器值为1,所以他并没有释放。 */ return 0; }
总结:
1.autorelease方法不会改变对象的引用计数器,只是将这个对象放到自动释放池中。
2.自动释放池实质是当自动释放池销毁后统一调用一次对象的release方法,不一定就能销毁对象(如果一个对象的引用计数器大于1就无法销毁)。
3.由于自动释放池最后统一销毁对象,因此如果一个操作比较占用内存(对象比较多或者对象占用资源比较多),最好不要放到自动释放池或者考虑放到多个自动释放池。
应用场景二:类方法快速创建一个本类对象
//Person.h文件 #import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic, copy) NSString *name; //创建对象的类方法声明 + (instancetype)personWithName:(NSString *)name; @end //Person.m文件 #import "Person.h" @implementation Person /* 创建对象的类方法实现 这个类方法以首字母小写的类名开头,如果没有参数就直接是类名,如果有参数就是类名WithXXX: 在类方法实现中创建对象同时并调用autorelease方法,这样调用者就可以不用再手动调用一次对象的autorelease方法。 */ + (instancetype)personWithName:(NSString *)name { //创建对象并调用一次autorelease Person *p = [[[Person alloc] init] autorelease];//这里调用了一次autorelease方法 p.name = name; return p; } //重写父类dealloc方法监控对象释放情况 - (void)dealloc { NSLog(@"%@释放了",self.name); [self.name release]; [super dealloc]; } @end //main.m文件 #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { //大括号代码自动释放池的范围,只对放在他内部的对象有效,对嵌套的无效 @autoreleasepool { //这里无需再调用autorelease方法 //并且这也符合内存管理原则,因为这里并没有alloc所以不需要release或者autorelease Person *p = [Person personWithName:@"肉丝"]; Person *p1 = [Person personWithName:@"迈克"]; //Apple写的类方法创建当前类的对象的方法无需再调用autorelease,因为Apple已经在实现类方法的时候已经调用过一次了 //比如下面这两个类方法 NSString *str = [NSString stringWithFormat:@"字符串"]; NSArray *array = [NSArray array]; /* 迈克释放了 肉丝释放了 */ } return 0; }
总结:
1.创建对象的类方法的方法名要遵守规范,以首字母小写的类名开头,如果没有参数就直接是类名,如果有参数就是类名WithXXX:。
2.在类方法实现中创建对象同时并调用autorelease方法,这样调用者就不需要手动释放。
3.OC类库中的类方法一般都不需要手动释放,内部已经调用了autorelease方法。
二、ARC机制
在OC中也有一种内存自动管理的机制叫做ARC(Automatic Reference Counting)自动引用计数,与C#、Java不同的是,这只是一种半自动的机制,有些操作还是需要我们手动设置的。要特别注意的是在ARC机制下不允许写retain、release、autorelease,并且在重写dealloc方法中不允许调用父类的dealloc方法,在编译器编译代码的时候会自动在需要的地方自动加上。
ARC机制以指向对象的指针强弱类型作为回收对象的基准,当没有任何强指针指向对象的时候对象就会被释放。创建一个对象默认就是强指针类型,当然我们也可以使用__strong和__weak(是两个下划线额)来显示的声明强指针和弱指针。
声明强类型的指针指向对象,再将强指针赋值nil
__strong Person *p = [[Person alloc] init]; p = nil;//p被赋值nil,对象没有任何指针指就直接释放
声明强类型的指针指向对象,再将弱类型的指针也指向对象,最后强指针赋值nil
__strong Person *p = [[Person alloc] init]; __weak Person *p1 = p; p = nil;//p被赋值nil,对象没有被任何强指针指就直接释放
直接声明弱类型的指针指向对象,对象会见光死
__weak Person *p = [[Person alloc] init];//对象创建就释放
三、单个对象的内存管理
我们知道当指向对象的所有强类型指针被回收的时候,对象也会立即被回收。同理,当所有指向对象的指针被赋值为nil的时候,对象也会被立即回收,并且还会为指向对象的弱指针的值自动赋值为nil。
Person *p1 = [[Person alloc] init]; Person *p2 = p1; Person *p3 = p2; __weak Person *p4 = p3;//让弱类型指针指向p3 p1 = nil;//p2、p3、p4还指向对象 p2 = nil;//p3、p4还指向对象 p3 = nil;//对象被回收,p4虽然还指向对象,但他是个弱类型指针
四、多个对象的内存管理
在ARC机制下,如果一个类的属性是另外一个类的类型的时候,这个属性一定是强指针。
//Person.h文件 #import <Foundation/Foundation.h> @class Car; @interface Person : NSObject //如果一个对象的属性是另外一个对象类型的时候,这个属性一定是强指针 @property (nonatomic, strong) Car *car; - (void)drive; @end //Person.m文件 #import "Person.h" #import "Car.h" @implementation Person - (void)drive { //调用车的run方法 [self.car run]; } - (void)dealloc { NSLog(@"人挂了"); } @end //Car.h文件 #import <Foundation/Foundation.h> @interface Car : NSObject - (void)run; @end //Car.m文件 #import "Car.h" @implementation Car - (void)run { NSLog(@"车在跑"); } - (void)dealloc { NSLog(@"车挂了"); } @end //main.m文件 #import <Foundation/Foundation.h> #import "Person.h" #import "Car.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [[Person alloc] init]; Car *car = [[Car alloc] init]; //将car给person person.car = car; //让car指针指向nil car = nil; //车依然能跑,因为person的car属性是强指针。 [person drive]; } /* 车在跑 人挂了 车挂了 */ return 0; }
注意:
1.在ARC机制下,@property参数retain就无法使用了,因为retian参数代表的意义是生成的set方法是标准的MRC下的内存管理代码。而ARC下,不需要引用记数,就不需要生成这些内存管理代码了。
2.在ARC机制下,属性的类型的OC对象类型的时候,一般使用strong,非OC对象的类型使用assign
3.strong、weak都是针对OC对象类型的属性,非OC对象不能使用strong、weak。
五、ARC机制下的循环引用问题
在ARC机制下,当两个类相互引用作为对方的属性的时候,如果两边的@property都使用strong就会出现循环引用而导致内存泄露。解决办法是一端使用strong,一端使用weak。
//Person.h文件 #import <Foundation/Foundation.h> @class Car; @interface Person : NSObject @property (nonatomic, strong) Car *car;//一端使用strong - (void)drive; @end //Person.m文件 #import "Person.h" #import "Car.h" @implementation Person - (void)drive { [self.car run]; } - (void)dealloc { NSLog(@"人挂了"); } @end //Car.h文件 #import <Foundation/Foundation.h> @class Person; @interface Car : NSObject @property (nonatomic, weak) Person *person;//一端使用weak - (void)run; @end //Car.m文件 #import "Car.h" @implementation Car - (void)run { NSLog(@"车在跑"); } - (void)dealloc { NSLog(@"车挂了"); } @end //main.m文件 #import <Foundation/Foundation.h> #import "Person.h" #import "Car.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [[Person alloc] init]; Car *car = [[Car alloc] init]; person.car = car; car.person = person; } return 0; }
六、MRC和ARC的兼容
如果在ARC模式的程序中,有一些类是使用MRC模式开发的(网上下载大神写的类是使用MRC写的),我们不想修改源文件让MRC和ARC能兼容,可以单独为MRC模式中开发的类设置不使用ARC机制。
七、MRC自动转为ARC
MRC模式的程序是可以直接自动转换为ARC模式的,不过转换的方式是删除所有的retain、relase、autorease、[super dealloc],将retain替换为strong。这样就会发生很多未知错误,所以请谨慎。
八、分类Category
当我们不改变原有代码为一个类扩展其他功能时我们可以考虑继承这个类进行实现,但是这样一来使用时就必须定义成新实现的子类才能拥有扩展的新功能。如何在不改变原有类的情况下扩展新功能又可以在使用时不必定义新类型呢?Apple提供了一种语法,在OC中使用分类Category可以对一个类进行分模块管理或扩充功能。
Category基本用法
//NSString+Extend.h #import <Foundation/Foundation.h> @interface NSString (Extend) //计算字符串中数字的个数的对象方法 - (NSUInteger)countOfNumber; //计算字符串中数字的个数的类方法 + (NSUInteger)countOfNumber:(NSString *)string; @end //NSString+Extend.m #import "NSString+Extend.h" @implementation NSString (Extend) //计算字符串中数字的个数的对象方法 - (NSUInteger)countOfNumber{ NSUInteger count = 0; for (int i = 0; i < self.length; i++) { unichar ch = [self characterAtIndex:i]; if (ch >= '0' && ch <= '9') { count++; } } return count; } //计算字符串中数字的个数的类方法 + (NSUInteger)countOfNumber:(NSString *)string { NSUInteger count = 0; for (int i = 0; i < string.length; i++) { unichar ch = [string characterAtIndex:i]; if (ch >= '0' && ch <= '9') { count++; } } return count; } @end //main.m文件 #import <Foundation/Foundation.h> #import "NSString+Extend.h" int main(int argc, const char * argv[]) { @autoreleasepool { NSString *str = @"432jhgfgbuy875jljf9dfs6dfsf4"; //调用扩充的方法计算一个字符串中阿拉伯数字的个数 NSUInteger count = [str countOfNumber]; //输出结果 NSLog(@"count = %lu",length); } /* count = 9 */ return 0; }
注意:
1.在分类里只能增加方法,不能增加成员变量。
2.在分类里可以写@property,但不会生成私有属性,所以在分类中不要使用@property。
3.在分类的方法实现中不可以直接访问本类的私有属性,需要通过(点语法)setter、getter方法访问。
4.当分类中有本类中同名的方法,会优先访问分类中的方法。
5.如果多个分类中有同名的方法,会优先访问最后编译的分类中的方法(编译顺序可在Xcode中设置,都知道就懒得上图了)。
6.为NSObject类或者其子类添加分类,就叫做非正式协议。