一、对象在内存中的存储细节
1.类创建对象,每个对象在内存中都占据一定的存储空间,有一份属于自己的单独的成员变量,所有的对象公用类的成员方法,方法在整个内存中只有一份。类本身在内存中占据一份存储空间,类的方法存储与此。
*堆栈的概念:栈一般由系统自动分配释放存放的是函数的参数值,局部变量等可以直接引用的数据,操作方式类似于数据结构中的栈;堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。
string str1=“111”;string str2="111";
这里string其实是一个类,但是str1和str2并不是对象,而是指向对象的引用,符串“111”才是对象。另外string是一个指针,所以str1和str2其实都是指针变量。当创建str1变量后,编译器内部会搜索有没有存放“111”这个字面值的地址空间。如果没有就开辟一个存放“111”。然后str1指向这个空间的地址。在创建str2之后,编译器搜索发现有存放“111”这个字面值的地址空间。所以直接指向这个空间的地址。不用开辟新的。
A a = new A();
这里在堆中new了一个空间给a。但是注意,a本身在栈中。alloc,copy是类似的。
2.每个对象都有一个隐藏指针isa,指向当前对象所属的类。当对象调用某个方法时,对象会顺着isa指针找到存储于类中的方法,然后执行。
注意,当一个类创建多个对象时,不同对象在内存中分配的是不同的存储地址,各成员属性地址也不相同。
二、引用计数器
1.每个OC对象都有自己的引用计数器(int类型,4字节),表示对象被引用次数。
使用alloc/new/copy创建一个新的对象时,新对象的引用计数器为1,当引用计数器为0时,对象占用的内存会被系统回收。
2.这里介绍四个方法:
1>retainCount 该方法返回当前计数器的值
2>retain 计数器加一,该方法返回对象本身
3>release 计数器减一,该方法没有返回值
4>dealloc 在对象计数器为0,它将被销毁,其占用的内存会被系统回收,此时系统会自动向对象发送该方法
注意:一般会重写dealloc方法,在这里释放相关资源。但重写dealloc方法时一定要调用[super dealloc]方法,并置于代码块的最后。不要使用dealloc方法来管理稀缺资源,比如文件,网络链接等。因为由于bug或者程序意外退出,dealloc方法不能保证一定会被调用。
3.当一个对象已经被释放,而某个指针依旧指向该对象,此时你使用这个指针可能会产生一个比较常见的错误:
EXC_BAD_ACCESS---坏访问错误(内存已被回收,不可用),也叫野指针错误。
(野指针:只想僵尸对象----不可用内存的指针)
解决方法就是在对象内存被回收之后,清空这个指针,令其等于nil
三、内存管理
1.内存管理的目的是:
1>不要释放或者覆盖还在使用的内存,这会引起程序崩溃;
2>释放不再使用的内存,防止内存泄露。iOS程序的内存资源是宝贵的。
注意:管理范围只对任何继承NSObject的对象,对其他的基本数据类型无效。
2.内存管理法则:
1>alloc、new或copy来创建一个对象,那么你必须调用release或autorelease。谁创建谁释放,对象所有权负责释放
2>只要还有人在使用某个对象,那么这个对象就不会被回收;只要你想使用这个对象,那么就应该retain让这个对象的引用计数器+1;当你不想使用这个对象时,必须release让对象的引用计数器-1
3>如果你在一个class的某个方法中alloc一个成员对象,且没有调用autorelease或及时releaase, 那么你需要在这个类的dealloc方法中调用release;如果调用 了autorelease,那么在dealloc方法中什么都不需要做
4>尽量用sel.xxx=xxx来对对象进行赋值操作,在对象不用的时候也可以用self.xxx = nil来对其释放
5>字符串是特殊的对象,但不需要release去手动释放对象,它默认是autorelease的,不用额外管理内存。
3.代码规范:
1>Set方法:
①基本数据类型
-(Void)SetAge:(int)age{
_age=age;
}
②OC对象类型
-(void)setBook:(Book *)book{
//1.先判断是不是新传进来的duix
if(_book!=book){
//2 对旧对象做一次release
[_book release];
//3.对新对象做一次retain
_book=[book retain];
}
}
2>dealloc方法:
-(void)dealloc{
[_bookrelease];
[super dealloc];
}
4.自动释放池(NSAutoreleasePool)
1>autorelease方法会返回对象本身,调用完autorelease方法后,对象的计数器不变。autorelease会将对象放到一个自动释放池中。NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声明为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。在iOS运行过程中,会创建无数个池子,这些池子都以栈结构存在,遵循先进后出原则,当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池。
- ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此对象加入autorelease pool
- NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数组,release数组中的所有对象。如果此时数组中成员的retainCount为1,那么release之后,retainCount为0,对象正式被销毁。如果此时数组中成员的retainCount大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。
- 2>autorelease好处:
- ①不再关心对象释放的时间
- ②不再关心什么时候调用release
- 3>autorelease注意:
- ①占用内存较大的对象不要随便使用autorelease。因为要等到释放池销毁才最终释放对象,可能会造成内存空间浪费
- ②占用内存较小的对象使用autorelease没有太大的影响
- 4>错误写法:
- ①alloc之后调用autorelease,又调用release
- @autoreleasepool{
- Person *p=[[[Person alloc]init]autorelease];
- [p release];
- }
- ②连续调用多次autorelease
- @autoreleasepool{
- Person *p=[[[[Person alloc]init]autorelease]autorelease];
- }
- 5.ARC
- 判断准则:只要没有强指针指向对象,对象就会被销毁
- 这里涉及到强弱指针的问题,一般指针默认为强指针,关键字strong;弱指针由_ _weak修饰:__weak NSString *str = @"aaa"。在ARC中,只要弱指针指向的对象不在了,就直接把弱指针作清空操作。
- ARC中,@property不再使用retain属性而使用strong,意味着成员变量是一个强指针,相当于以前的retain;弱指针则换成weak属性。在dealloc中不再需要[super dealloc],也不许再调用release,retain,retainCount方法。
- 注意: __weak Person *p=[[Person alloc]init];
- //这个写法不合理,因为对象一创建出来就被释放了,而对象被释放之后,ARC把指针清空。
四、@property 和@synthesize
[email protected]现在基本上已经不用写了,它是用来在.m文件中实现成员变量的set和get方法的。但是如果@synthesize,变量名要先在.h中声明。
[email protected]这个关键字用来在.h中声明成员变量,Xcode4.4以后它做了增强,当我们使用@property进行声明,类会自动帮我们实现。如果没有手动声明成员变量,property会在.h文件中自动帮我们生成一个_开头的成员变量。注意:如果想要子类继承父类的成员变量,还是要在.h中手动写入成员变量的
例:@property int age
@synthesize age
展开形式:
如果手动实现set方法,那么编译器就只生成get方法和成员变量;如果手动实现了get方法,那么编译器就只生成set方法和成员变量;如果手动实现了get和set方法,那么编译器将不会报错并且报错。
property有几个属性,我们来看一下:
1>多线程管理
nonatomic 我们一般都选择这个属性,因为性能比较高,它表示在get,set方法中不添加多线程管理
atomic 这个属性是默认属性,性能比较低,表示在get,set方法中添加多线程管理
2>set方法内存管理相关的参数
retain 在生成的set方法中,release旧值,retain新值,适用于OC对象
例: if(_book!=book)
{
[_book release];
_book=[book retain];
}
assign 直接赋值,默认属性,但我们最好写出来,适用于非OC对象
copy release旧值,copy新值
3>是否要生成set方法
readwrite 默认,同时生成set和get的声明,实现
readonly 只读,只生成get的声明和实现
4>set和get方法的名称
setter : 决定set方法的名称,一定要有冒号
getter : 决定get方法的名称,一般用BOOL类型,以is开头
例:@property(setter=setName,getter=isRich) BOOL rich;
BOOL b=Jack.isRich; //调用
3.在这里补充一下循环引用的知识:循环引用就是两个类互相引用,如果两边的.h文件都导入对方的.h头文件,那么久会造成死循环。解决方法有几种:
1>在两边.h头文件中不要用#import导入对方的.h文件,而是用@Class关键字去告诉编译器这是一个类
比如: @Class Person //仅仅告诉编译器,Person是一个类
在实际开发中,引用一个类的时候,有这样一个规范:
①在.h文件中用@Class关键字来声明这个类
②在.m文件中用#improt来包含这个类的所有东西
2>两端循环引用时,切断他们之间的联系,在一方中定义属性的时候,@property中不使用retain而使用assgin,使用assign的在dealloc方法中不再调用release方法
3>ARC中使用弱引用来避免对一个对象发送retain消息会创建对这个对象的强引用。