NSString中的内存管理问题
由于autoreleasepool的存在,对于内存管理就会很复杂,retainCount 不能作为调试内存时的依据。
所以一般来说NS开头的类(或者说系统自己内部提供的类)基本上不需要我们做太多的内存管理,因为我们很难检测出来。
比如:
NSString *str=[[NSString alloc]initWithString:@"123123"];
NSLog(@"str retainCount=%tu",[str retainCount]);
输出的结果是:str retainCount=18446744073709551615
这里的值不是乱码,而是很大,简单的release一下根本不会使这个值改变(一般一点不会改变),所以我们不要对这种系统中原有的类型进行内存操作。
if(retainCount>0)
{
[str release];
}
这样很容易死循环。
———————————————————————————————————————————
autorelease 基本使用
(1)自动释放池 及 autorelease介绍
自动释放池:
①在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的
②当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中
(2)自动释放池的创建方式
iOS 5.0 以后
@autoreleasepool
{ //开始代表创建自动释放池
………
} //结束代表销毁自动释放池
(3)autorelease的原理
实际上autorelease只是把对release的调用延迟了,对于每一个autorelease,系统只是把该object放入了当前的autorelease pool中,当该pool释放时,该pool中所有的object会被一起调用release。
代码:
#import <Foundation/Foundation.h>
@interface Car : NSObject
-(void)run;
@end
@implementation Car
-(void)run
{
NSLog(@"Car run!");
}
- (void)dealloc
{
NSLog(@"Car dealloc!");
[super dealloc];
}
@end
int main(int argc, const char * argv[]) {
// 创建一个自动释放池
@autoreleasepool {
Car *car=[[[Car alloc]init]autorelease];//首先要用autorelease,那就一定得将ARC关掉
[car run];
[car run];
[car run];
// 单单是创建对象,如果调用autorelease,那么程序结束的时候会自动释放每一个对象
// 如果是在其中retain了对象,记住,如果retain就要release一下,否则是无法释放内存的。
// [car retain];
// [car release];
}//执行的此处的时候,会对释放池中的每一个对象进行一次release操作
return 0;
}
———————————————————————————————————————————
自动释放池的 使用误区/嵌套/注意事项
#import <Foundation/Foundation.h>
@interface Car : NSObject
-(void)run;
@end
@implementation Car
-(void)run
{
NSLog(@"Car run!");
}
- (void)dealloc
{
NSLog(@"Car dealloc!");
[super dealloc];
}
@end
void test1()
{
Car *car2=[[[Car alloc]init]autorelease];//此时car2不能释放,因为autorelease没有在autorelease的代码块中调用
Car *car3=[[Car alloc]init];
@autoreleasepool {//自动释放池代码块
// 误区①:并不是将对象放到自动释放池代码块内部它就能自动释放的,只有手动调用autorelease方法以后,才能把对象加入到自动释放池
// Car *car1=[[Car alloc]init];
// [car1 run];
// 上面的代码是无法释放car1的
// 误区②:如果调用了autorelease,但是这个调用的语句并没有放到自动释放池代码块中,那么也无法将对象加入到自动释放池
[car3 autorelease];//car3可以被释放
}
}
int main(int argc, const char * argv[]) {
// 自动释放池的嵌套
Car *car1=[[Car alloc]init];
[car1 run];
[car1 retain];//如果要在此处加上retain,让car1的retainCount变为2,那么就要在@autoreleasepool {//自动释放池1
@autoreleasepool {//自动释放池2
@autoreleasepool {//自动释放池3
[car1 autorelease];
}//显然在这里执行了 [p release]; 的操作
// [car1 autorelease];
// 但是有一点 [car1 autorelease]; 不可以过多执行,如果retainCount为0了还执行的话,那么就会出错了~
}
}
return 0;
}
自动释放池嵌套在内存中的实现看下图:
★注意:
①自动释放池不能放占用内存较大的对象,也不要将大量循环放到同一个@autorelease之间,这样会造成内存峰值的上升②@autorelease {
//如果不写这里的自动释放池代码块,则在ARC机制下无法释放内存
}
所以必须加这句话,不要将编译器自动生成的这句话删掉
③[car1 autorelease]; 之后,就不要再 [car1 release]; 了,也不要 [car1 autorelease]; ,这样都是多余的,会报错,上面也提到了。所以说要么使用自动释放池,要么使用手动内存管理。
———————————————————————————————————————————
autorelease的应用场景——利用autorelease建立一个快速创建对象的方法
首先,快速创建实例对象的方法通常是一个类方法,以小写的类名命名。下面展示两种快速创建实例对象的方法,无参和有参。
声明:
+(instancetype)bigCar; //无参数的
实现:
+(instancetype)bigCar
{
return [[[self alloc]init]autorelease];
}
或者是
声明:
+(instancetype)bigCarWithSpeed:(int)speed; //含参数的
实现:
+(instancetype)bigCarWithSpeed:(int)speed
{
return [[[self alloc]initWithSpeed:speed]autorelease];
}
但是写成上面这样的有参数的快速创建实例对象的格式,需要重写父类的构造方法(自定义构造方法),如下:
-(instancetype)initWithSpeed:(int)speed
{
if(self=[super init])
{
_speed=speed;
}
return self; //提醒一下,return self ; 要写在if大括号的外面~
}
代码:
#import <Foundation/Foundation.h>
@interface Car : NSObject
-(void)run;
+(instancetype)car;
@end
@implementation Car
-(void)run
{
NSLog(@"Car run!");
}
- (void)dealloc
{
NSLog(@"Car dealloc!");
[super dealloc];
}
//方法①
//+(Car *)car
//{
// return [[[Car alloc]init]autorelease];
//}
//方法①是不可取的,因为如果有一个BigCar类继承Car类,我们在main中作了如下操作
//BigCar *bigcar1=[BigCar car];
//[bigcar1 run];
//显然我们方法①中是用Car开辟的内存空间,故创建出来的还是Car的实例对象。所以无法调用BigCar中的run方法,调用的还是Car的(父类的)
//方法②
//+(id)car
//{
// return [[[self alloc]init]autorelease];
//}
//方法②中,我们将调用创建内存空间和初始化的类设置为self,也就是当前类。将返回值类型设置为id类型,这样的话我们就可以利用父类的快速创建实例对象的方法去创建子类的实例对象了。但是还是有些不足,我们再看下面这个例子。
//NSString *str=[BigCar car]; 我们用BigCar类去快速创建了一个实例变量,返回值是BigCar类型的,但是我们是将返回值赋给了str,str明明是NSString类型的,自然会出错。但是!这里在编译的时候检测不出来,也没有警告,所以这里还是不合适,需要改进
//方法③
+(instancetype)car
{
return [[[self alloc]init]autorelease];//这里是self,也就是这里用的是当前类创建实例对象
}
//最后我们完善了最后的方法③,这个方法是将返回值类型设置为instancetype类型,这样相对于方法②而言优点就是能在编译的时候判断两边的类型是否一致,遇到上面的情况,就会在编译的时候发出警告了!
//所以,我们以后快速创建实例对象的时候要用方法③
@end
@interface BigCar : Car
@end
@implementation BigCar
-(void)run
{
NSLog(@"BigCar run!");
}
- (void)dealloc
{
NSLog(@"BigCar dealloc!");
[super dealloc];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 先将ARC关闭
// Car *car1=[[[Car alloc]init]autorelease];
// [car1 run];
// 我们要利用autorelease建立一个快速创建实例对象的方法,就是要代替上面 Car *car1=[[[Car alloc]init]autorelease]; 这句话
BigCar *bigcar1=[BigCar car];
[bigcar1 run];
}
return 0;
}
快速创建实例对象的方法(代码2——这部分对上面的代码做了优化,将 自定义构造方法 和 快速创建实例对象 的方法都做了传值处理,熟练书写的过程)
代码2:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property NSString *name;
@property int age;
-(instancetype)initWithName:(NSString *)name andWithAge:(int)age;
+(instancetype)person:(int)age;
@end
@implementation Person
-(instancetype)initWithName:(NSString *)name andWithAge:(int)age;
{
if(self=[super init])
{
_name=name;
_age=age;
}
return self;
}
+(instancetype)person:(int)age
{
return [[self alloc]initWithName:@"wang" andWithAge:age];
//name 和 age 接收参数的不同点就是age是传进来的,是我们在main中赋值的,传进来的age直接赋给_age。而name是我们在 快速创建实例对象的方法 中进行赋值的,直接将赋好的 wang 传递给_name,所以说两个赋值的方式只是赋值的位置不同而已。
//不管哪种形式,我们都要知道,我们写的这个快速创建实例对象的方法在内部是调用自定义构造方法的!然后在自定义的构造方法中返回创建好的实例对象。
}
@end
@interface Student : Person
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p=[Person person:12];
NSLog(@"person.name=%@,person.age=%d",p.name,p.age);
Student *stu=[Student person:12];
NSLog(@"student.name=%@,p.age=%d",stu.name,stu.age);
}
return 0;
}
———————————————————————————————————————————
版权声明:本文为博主原创文章,未经博主允许不得转载。