内存管理
内存管理基本原理
• 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。 比如回收一些不需要使用的对象、变量等
<1>回顾c语言中的内存管理
int fun(int x){
return x+10;
}
int main()
{
char *str=malloc(100);
int a=100;
fun(a);
}
栈空间中(系统函数开始的时候自动申请,结束的时候自动释放)
局部变量,函数参数
静态存储区(程序开始时为这些拜年了申请空间,结束时释放)
全局变量,静态变量
堆空间(malloc手工申请,使用free手工释放)
malloc申请的空间在堆中
如果malloc申请的内存没有free?
会出现什么问题:内存泄漏
<2>oc中内存管理方式简介
两类内存管理方式:手动内存管理和arc(自动引用计数)
为什么学习手动内存管理
(1)面试的时候可能考察手动内存管理
(2)有些公司使用手动内存管理,以前的代码使用手动内存管理
(3)部分开源库也是使用手动内存管理的
<3>引用计数的内存管理方式
遇到的问题:多个指针指向同一个对象小技巧:如何切换程序的内存管理方式
Project-->Build Setting--->搜索counting 找到选项 设置为YES或者NO
YES 表示使用自动引用计数
NO表示使用手动内存管理
思路:
第一件事:定义一个对象指针dog
第二件事:创建一个对象
这个对象的内存的哪? alloc 申请的内存一般都在堆上
alloc 也需要释放
(1)解决了多个指针指向同一个对象的释放问题
(2)oc中对象都有一个引用次数retainCount
(3)每次一个指针指向了这个对象 则这个对象的引用次数+1
(4)如果一个指针不在引用这个对象,则这个对象的引用次数-1
(5)最后,没有指针引用这个对象,则释放对象
创建对象的时候retainCount==1 表示有一个指针引用了这个对象
NSLog(@"%lu",dog.retainCount);
release作用:
(2)如果这个对象retainCount==1 需要释放这个对象
Dog *anDog=[dog retain];
retain方法作用:使对象的引用次数+1;
[anDog retain];
NSLog(@"%lu",dog.retainCount);
[dog release];
NSLog(@"%lu",dog.retainCount);
[anDog release];
不要这样使用,这样使用时有问题的
NSLog(@"%lu",dog.retainCount);
思考:什么叫释放内存???
放弃了这块内存的使用权
什么是申请内存???
获取了这块内存的使用权
#import <Foundation/Foundation.h>
@interface Dog : NSObject
{
NSString *_name;
int _age;
}
@property (copy)NSString *name;
@property int age;
@end
#import "Dog.h"
@implementation Dog
//重写dealloc方法,验证对象释放被释放
// 实现的 时候有特殊要求,方法的最下面执行父类的dealloc
//作用:对象被释放的时候由系统自动调用
-(void)dealloc
{
NSLog(@"dog dealloc");
[super dealloc];
}
@end
<4>字符串内存管理
//字符串的内存管理
// 如果是字符串常量对象,retainCount是一个极大值
//无需retain也无需release
NSString *str=@"chang huai yi ke ping chang xin";
NSLog(@"NSString 的 retainCount 值是:%lu",str.retainCount);
//输出:18446744073709551615
NSString *str2=[[NSString alloc] initWithString:@"123"];
NSLog(@"NSString 的 retainCount 值是:%lu",str2.retainCount);
[str2 release];
//输出:18446744073709551615
NSMutableString *str3=[[NSMutableString alloc] initWithString:@"123"];
NSLog(@"NSString 的 retainCount 值是:%lu",str3.retainCount);
// 输出:1
[str3 release];
NSMutableString *str4=[[NSMutableString alloc] initWithUTF8String:"ooooo"];
NSLog(@"NSString 的 retainCount 值是:%lu",str4.retainCount);
//输出:1
[str4 release];
总结:字符串内存管理方式
(2)如果这个字符串是新创建的(不是指向一个常量字符串对象)他的引用次数是1需要用 release释放
以后如何处理:如果这个字符串对象时alloc创建的,使用release释放即可
不是通过alloc创建的,不需要释放
<5>autorelease方法,自动释放作用 //自动释放池
@autoreleasepool {
//autorealese 的使用
//autorealese的原理:会把对象加入到自动释放池
Dog *dog=[[[Dog alloc] init] autorelease];
dog.age=100;
NSLog(@"%d",dog.age);
//[dog release];
//给一个对象发送了autorelease之后不会立即释放,而是使用完之后释放
//[dog autorelease];
}
NSLog(@"end");
//输出的结果分别是100,dealloc,end 所以是释放完之后再结束
return 0;
<6>copy和mutableCopy方法,复制一个对象
需求:获取一个对象,不让其他指针改变
解决: 使用copy方法或者mutableCopy复制
// copy方法和mutableCopy的使用
NSMutableString *str=[[NSMutableString alloc] initWithString:@"yanyan"];
// NSMutableString *name=[str retain];
//出现的问题:A指针和B指针指向同一个对象
// 导致A指针修改对象,B指针得到的是修改后的值
//copy返回一个不可变的对象 mutableCopy返回一个可变的对象
NSMutableString *name=[str mutableCopy];
[str setString:@"yanzi"];
NSLog(@"%@",name);
[str release];
[name release];
内存管理原则---黄金法则
原则:如果一个对象使用了alloc new retain copy mutableCopy 那么你必须使用想要的release和autonrelease
代码中出现的各种情况
(1)如果实在方法的内部定义的对象指针,指向了alloc申请的一个对象
何时释放: 使用完之后释放,可以放在方法最后
方法内部申请对象空间,方法结束的时候release释放即可
(2)init中申请对象指针(类的实例变量)的申请对象需要在dealloc方法中释放(重点)
{
Engine *_engine;
//包含过个轮胎
NSMutableArray *_tireArray;
}
-(id)init
{
if (self = [super init]) {
//申请内存
_engine=[[Engine alloc] init];
_tireArray = [[NSMutableArray alloc] init];
}
return self;
}
-(void)dealloc
{
NSLog(@"Car dealloc");
//释放内存
[_engine release];
[_tireArray release];
[super dealloc];
}
(3)对象指针的setter方法
如果你有个OC对象类型的成员变量,就必须管理这个成员变量的内存。比如有个Book *_book
//添加设置方法
-(void)setEngine:(Engine *)engine
{
//避免两次传染的时同一个对象
if (_engine!=engine) {
//_engine指向新的对象之前有可能指向一个对象
[_engine release];
//类中的实例变量—_engine指向外部传入的对象engine 多个指针指向同一个对象
_engine=[engine retain];
}
}
(4)对象制作的 getter方法
-(Engine *)engine
{
//不能两个指针指向一个对象 同事不能立即释放
return [[_engine retain] autorelease];
}
(5)各种变量的property(重点)
@property参数
• 控制set方法的内存管理
• retain : release旧值,retain新值(用于OC对象)
• assign : 直接赋值,不做任何内存管理(默认,用于非OC对象类型)
• copy : release旧值,copy新值(一般用于NSString *)
• 控制需不需生成set方法
• readwrite :同时生成set方法和get方法(默认)
• readonly :只会生成get方法
• 多线程管理
• atomic :性能低(默认)
• nonatomic :性能高
• 控制set方法和get方法的名称
• setter : 设置set方法的名称,一定有个冒号:
• getter : 设置get方法的名称
#import <Foundation/Foundation.h>
#import "Engine.h"
@interface Car : NSObject
{
double _speed;
NSString *_name;
Engine *_engine;
//包含过个轮胎
NSMutableArray *_tireArray;
}
//基本数据类型一般都用 assign
//设置方法中直接设置值
//获取方法中直接返回值
// assign是默认的,可写可不写
@property (assign)double speed;
//字符串一般都用copy
// 表示吧传染的字符串复制一份
@property (copy)NSString *name;
//对象指针一般用retain
// 设置方法先释放以前指向的对象,再指向新的对象(retain一次)
//获取方法中 先retain再release
@property (retain)Engine *engine;
添加设置方法
//-(void)setEngine:(Engine *)engine;
添加获取方法
//-(Engine *)engine;
@end
#import "Car.h"
@implementation Car
-(id)init
{
if (self = [super init]) {
//申请内存
_engine=[[Engine alloc] init];
_tireArray = [[NSMutableArray alloc] init];
}
return self;
}
//添加设置方法
-(void)setEngine:(Engine *)engine
{
//避免两次传染的时同一个对象
if (_engine!=engine) {
//_engine指向新的对象之前有可能指向一个对象
[_engine release];
//类中的实例变量—_engine指向外部传入的对象engine 多个指针指向同一个对象
_engine=[engine retain];
}
}
//添加获取方法
-(Engine *)engine
{
//不能两个指针指向一个对象 同事不能立即释放
return [[_engine retain] autorelease];
}
-(void)dealloc
{
NSLog(@"Car dealloc");
//释放内存
[_engine release];
[_tireArray release];
[super dealloc];
}
@end
(6)类方法创建对象如何写
特性:类方法创建对象会自动释放,不需要release
+(id)car
{
Car *newCar=[[Car alloc] init];
//(2)返回对象 类方法创建的对象加了autorelease之后会自动释放内存
return [newCar autorelease];
}
main 中 不用写释放
Car *car=[Car car];
(7)数组中的添加对象内存管理 NSArry
//NSArray 存储对象的内存管理
Car *car1=[[Car alloc] init];
Car *car2=[[Car alloc] init];
//数组中加入一个对象,这个不仅会保存对象的指针 还会把对象引用次数retiancount+1
NSArray *arr=[[NSArray alloc] initWithObjects:car1,car2,nil];
NSLog(@"car1 rc=%lu",car1.retainCount);
//释放数组的时候,数组对其中每个对象发送release消息
[arr release];
[car1 release];
[car2 release];
(8)取消线程保护 nonautomic
多线程再访问同一个实例变量的时候会出现问题
nonautomic去掉了多线程保护,提高了代码的运行速度
@class和#import的区别
• #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息
• 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方式就不会出现这种问题了
• 在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类