天天看点

objective-C学习笔记(五):OC之内存管理

一、对象在内存中的存储细节 

     1.类创建对象,每个对象在内存中都占据一定的存储空间,有一份属于自己的单独的成员变量,所有的对象公用类的成员方法,方法在整个内存中只有一份。类本身在内存中占据一份存储空间,类的方法存储与此。

objective-C学习笔记(五):OC之内存管理

      *堆栈的概念:栈一般由系统自动分配释放存放的是函数的参数值,局部变量等可以直接引用的数据,操作方式类似于数据结构中的栈;堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由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

     展开形式:   

objective-C学习笔记(五):OC之内存管理

      如果手动实现set方法,那么编译器就只生成get方法和成员变量;如果手动实现了get方法,那么编译器就只生成set方法和成员变量;如果手动实现了get和set方法,那么编译器将不会报错并且报错。

objective-C学习笔记(五):OC之内存管理

       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消息会创建对这个对象的强引用。