Day2 oc内存相关

 

1、内存分配(摘自千峰黎老师课程)

Person *xiaoming =[[Person alloc] init];//在堆上分配空间
xiaoming->age = 20;

Person *xiaowang = xiaoming;
xiaowang->age =30;
int a=xiaoming->age;//这里age为30,因为指向同一内存空间


2、内存字节对齐

(摘自http://blog.csdn.net/hairetz/article/details/4084088)

1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。

 

2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)

 

3:收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍.不足的要补齐. 

typedef struct bb
{
 int id;             //[0]....[3]
 double weight;      //[8].....[15]      原则1
 float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3
}BB;

typedef struct aa
{
 char name[2];     //[0],[1]
 int  id;         //[4]...[7]          原则1

 double score;     //[8]....[15]    
 short grade;    //[16],[17]        
 BB b;             //[24]......[47]          原则2
}AA;

int main()
{
  AA a;
  cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;
  return 0;
}

 结果是

48 24

 #pragma pack().

在代码前加一句#pragma pack(1),上面的代码输出为

32 16
bb是4+8+4=16,aa是2+4+8+2+16=32;

#pragma pack(1),告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则.

 

3、构造和析构函数

  1. oc的构造函数以initWithXXX开头,PS:OC中自定义初始化方法的名称必须是以init开头
    -(id)init{//重写父类init
    if(self=[super init]){//面向对象继承的概念,一个子类从父类继承,利用父类的init方法为子类实例的父类部分属性初始化;防止初始化失败返回nil
    NSLog(@"在%s构造函数中",_FUNCTION_);
    }
    }
     
  2. 析构函数dealloc(系统自动调用,不能手动调用)
    -(void)dealloc{//对象销毁时系统自动调用,即对象计数器=0,可以调用[对象 release]
    NSLog(@"release");//code here before dealloc
    [super dealloc];
    }
     
  3. +(void)initialize初始化全局变量的时候可重写,类方法在该类第一次实例化时调用

4、内存管理的黄金法则

(摘自http://www.jianshu.com/p/137dac08d7e8)
  • 管理范围

任何继承了NSObject的对象
内存管理的5大区域,其中栈(放局部变量)、堆(动态分配的)
Person *p = [[Person alloc] init];
其中指针p是局部变量放栈中,Person类对象放堆中。栈中变量一旦作用域结束即回收,堆中的如Person类对象并不会自动回收。
如果一个对象使用了alloc,new,[mutable]Copy,retain,那你就必须使用相应的[auto]release。
内存管理对于C语言基本类型(如int,short,char,struct,enum,union等类型)基本无效,任何继承于NSObject类的对象都属于OC类型,所有OC对象都有一个计数器,4个字节的空间来计数当前被引用的次数。
  • C和C++内存管理
当有多个指针同时指向同一块内存的时候,任何一个指针调用free方法释放内存,其余指针成为野指针,在不知情的情况下继续使用这块内存就会出现问题,造成C和C++在内存泄露。
int *p1 = malloc(100);
int *p2 = p1;
int *p3 = p1;
Free(p2);//p1,p3指向的内存空间已经不存在,p1和p3成为野指针

 

  • OC内存管理

所有的OC类型对象的结构如下,这个对象的内存在包含自己的变量和方法的基础上还包含一个retainCount的引用计数,每一个OC对象都有一个4字节的retainCount的计数器。表示当前对象被引用的次数,如果引用次数为0,就会调dealloc释放这块内存。

Person *p = [[Person alloc] init];
NSUInteger count = [p retainCount];//计数器的值
[p retain];//返回对象本身
[p release];
[p release];//没有返回值
//[p retain];对已释放的类对象做retain操作,点击项目左上角,打开Edit scheme-》diagnostics -》enable zombie object,会在运行时检测僵尸对象,会报错。
//p = nil;为了避免下面的错误,oc不像java没有空指针错误
[p release];//此时会报EXC_BAD_ACCESS错误(指向不存在的内存,野指针)

 僵尸对象:所占内存已经被回收的对象

野指针:指向僵尸对象的指针,给野指针发送消息会报错

空指针:没有指向任何东西的指针,存储的是nil、NULL、0,给空指针发送消息不会报错

规则:
1、Objective-C类中实现了引用计数器,对象知道自己当前被引用的次数.
2、最初alloc对象的计数器为1.
3、如果需要引用对象,可以给对象发送一个retain消息,这样对象的计数器就加1.
4、当不需要引用对象了,可以给对象发送release消息,这样对象计数器就减1.
5、当计数器减到0,自动调用对象的dealloc函数,对象就会释放内存.
6、计数器为0的对象不能再使用release和其他方法.

ps:谁创建谁release,谁retain谁release



比如有一个引擎类Engine,有一个类Car,Car里面有一个Engine的实例变量,一个setter个getter方法,如下所示

#import "Car.h"  

@implementation Car

- (void) setEngine : (Engine*) engine
{
    _engine = engine;
}

- (Engine*) engine
{
    return _engine; 
}

- (void) dealloc
{
    NSLog(@"Car is dealloc");
    [super dealloc];
}
@end

 上面写的是一个简单的类,但是这样写是有问题,需要一步步的改进。

第一步改进:
先使用它看问题的所在,在main方法里面如下使用
//先创建一个引擎
Engine* engine1 = [[Engine alloc]init];
[engine setID : 1];
//再创建一个汽车,设置汽车的引擎
Car* car = [[Car alloc]init];//retainCount=1
[car setEngine : engine];
/*
分析:在这里,现在有两个引用指向这个Engine对象,engine1和Car中的_engine,可是这个Engine对象的引用计数还为1,因为在setter方法中,并没有使用retain。那么不管是那个引用调用release,那么另外一个引用都将指向一块释放掉的内存,那么肯定会发生错误,所以需要在setter方法中改进。
*/
  第二步改进:
setter方法改进
- (void) setEngine : (Engine*) engine
{
    _engine = [engine retain];//多一个引用,retainCount+1 
}
 再在main中使用它
//先创建一个引擎
Engine* engine1 = [[Engine alloc]init];
[engine1 setID : 1];
//再创建一个汽车,设置汽车的引擎
Car* car = [[Car alloc]init];//retainCount=1
[car setEngine : engine1];//retainCount=2,因为setter中使用了retain

//假设还有一个引擎
Engine* engine2 = [[Engine alloc]init];
[engine2 setID : 2];

//这个汽车要换引擎,自然又要调用setter方法
[car setEngine : engine2];
/*分析:这里换了一个引擎,那么它的engine就不再指向engine1的那个对象的内存了,而是换成了engine2,也就是说engine1的那块对象指向的内存的引用只有一个。可是它的retainCount是两个,这就是问题所在,所以仍需要改进*/
 第三步改进:
- (viod) setEngine : (Engine*) engine
{
    [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release掉
    _engine = [engine retain];//多一个引用,retainCount+1
}
再在main中使用
//先创建一个引擎
Engine* engine1 = [[Engine alloc]init];
[engine1 setID : 1];
//再创建一个汽车,设置汽车的引擎
Car* car = [[Car alloc]init];//retainCount=1
[car setEngine : engine1];//retainCounr=2,因为使用了retain,所以retainCount=2

//如果进行了一个误操作,又设置了一次engine1
[car setEngine : engine1];

/*分析:那么,又要重新调用一次setter方法,这根本就是无意义的操作,所以要在设置之间加上判断*/
 第四步改进:
- (void) setEngine : (Engine*) engine
{
    if(_engine!=engine){//判断是否重复设置
        [_engine release];//在设置之前,先release,那么在设置的时候就会自动将前面一个引用release掉
        _engine = [engine retain];//多了一个引用,retainCount+1
    }
}
  第五步改进:
现在setter方法基本没有问题了,那么当我们释放掉一个car对象的时候,也要释放它里面的_engine的引用,所以要重写Car的dealloc方法。
- (void) dealloc
{
    [_engine release];//在释放car的时候,释放掉它对engine的引用
    [super dealloc]; 
}
 这还不是最好的方法,下面的方法更好
- (void) dealloc
{
     [self setEngine : nil];//释放car得分时候,使用setEngine设为nil,它不仅会release掉,而且指向nil,即使误操作也不会出错
     [super dealloc];
}
 所以,综上所述,在setter方法中的最终写法是
- (void) setEngine : (Engine*) engine
{
    if(_engine!=engine){//判断是否重复
    [_engine release];//设置之前,先release,在设置的时候,就会自动将前面的引用release掉
    _engine = [engine retain];
    }
}
 也可以是:
- (void) setEngine : (Engine*) engine   
{
    [engine retain]; //一定注意retain和release的先后顺序,不然engine和_engine相同时会出错
    [_engine release];
    _engine = engine;
}
 然后在dealloc方法中的写法是
- (void) dealloc
{
    [self setEngine:nil];
    [super dealloc];
}
  • property中setter的关键字

http://magicbird.iteye.com/admin/blogs/2279824,可参照这篇有适用的具体说明。


在property中有三个关键字定义关于展开setter方法中的语法,assign(缺省),retain,copy.当然这三个关键字是互斥的。
1.assign展开setter的写法
- (void) setEngine : (Engine*) engine
{
    _engine = engine;
}
2.retain展开的写法
- (void) setEngine : (Engine*) engine
{
    if(_engine!=engine){//判读断是否重复设置
        [_engine release];//设置之前,先release,那么在设置的时候就会自动将前面一个引用release掉
        _engine = [engine retain];//多了一个引用,retainCount+1
    }
}
3.copy展开的写法
- (void) setEngine : (Engine*) engine
{
    if(_engine!=engine){//判断是否重复设置
        [_engine release];//设置之前先release
        _engine = [engine copy];//多了一个引用,retainCount+1
    }
}
对于copy属性有一点要注意,被定义有copy属性的对象必须要符合NSCopying协议,并且你还必须实现了-(id)copyWithZone:(NSZone*)zone该方法。

可以看到,使用retain和我们上面举的例子完全相同,所以我们可以使用property和它的retain代替之前的写法。

1、set方法内存管理的相关参数
retain:release旧值,retain新值(适用于oc对象类型)
assign:直接复制(默认,适用于非oc对象类型)
copy:release旧值,copy新值
2、是否要生成set方法
readwrite:生成setter和getter(默认 )
readonly:只会生成getter
3、多线程管理
nonatomic:性能高(一定要加)
atonic:性能低(默认)
4、setter和getter方法的名称
@property(getter = aaa,setter = setAaa:)  int number;//重命名setter一定有冒号
p.number = 1;
p.aaa = 1;//都可以
@property(getter = isRich) BOOL rich;//一般这样用

猜你喜欢

转载自magicbird.iteye.com/blog/2279022
今日推荐