zoukankan      html  css  js  c++  java
  • OC语言--内存管理

    1.内存管理原理的介绍

    1.1C的内存管理

    char *p = (char *)malloc(100*sizeof (char)); 

    这是C的动态内存分配,我们手动跟系统申请了100个字节的内存;或者说系统在堆里开辟了100个字节的空间,并将这个空间的首地址返回给指针变量p。

    strcpy(p,"Hello World!");

    将字符串拷贝给指针变量p指向的内存空间。

    puts(p);

    将p指针指向的内存空间里的字符串打印出来。

    free(p);

    使用完成后,手动跟系统释放内存空间;或者说系统回收空间。如上就是C里简单的内存管理。

    C的内存管理,我们手动申请,手动释放。

    这样来看,我们只需要注意两个问题就好了:

    1,申请内存,使用完成后需要释放,如果不释放会造成内存泄漏。

    2,不能多次释放,如果多次释放,则会崩溃。

    但是,如果项目比较复杂,需要有几十上百号人一起分工完成,就很容易出现问题。

    比方说我们开辟了一块内存空间,里面存放了一块很有用的数据。但是,这个数据不只有我在这一块代码里用,甚至有多个人,在程序的多个地方使用。这样造成的结果就是,就算我使用完成这块内存,我也不能去释放他,因为我不能确定,别人在别的地方是否还需要使用这块内存。内存泄露在所难免了。

    2.OC的内存管理方式:引用计数

    2.1引用计数

    对于一块动态申请的内存,有一个人(指针)使用,就给这个内存的计数器(该计数器在该对象中)加1,使用完成后,就给这个计数器减1,当这个内存的引用计数为0了,我们则释放他,这样,上面的问题就解决了。OC,就是使用引用计数这种方式来管理内存的。

    2.2内存管理的黄金法则

    对于引用计数来说,有一套内存管理的黄金法则:

    The basic rule to apply is everything that increases the reference counterwith alloc, [mutable]copy[withZone:] or retain is in charge of the corresponding [auto]release.

    如果对一个对象使用了alloc、[mutable]copy、retain,那么你必须使用相应的release或者autorelease。

     通俗一点的说法就是谁污染谁治理。

    2.3MRC和ARC

    ARC Automatic Reference Counting,自动引用计数,由xcode,帮我们去管理内存。

    MRC Manual  Reference Counting,手动引用计数,由我们手动管理内存。

    但就目前来看,很多公司还是使用MRC.

    2.4 如何将工程改为MRC

    xcode5,工程创建的时候是ARC的,我们如果想要MRC,需要进行如下设置。

    选中工程 - >target - >Bulid Settings - >搜索:automatic reference counting或auto,将Objective-C Automatic Reference Counting改为NO。

    3.手动内存管理的操作(MRC)

    3.1alloc与release

    创建一个新的工程,先别将内存管理改为手动

    创建一个Dog类

      

    @interface Dog : NSObject
    
      @end
    
     
    
      @implementation Dog
    
      - (void)dealloc
    
      {
    
        NSLog(@"dog dealloc");
    
        [super dealloc];
    
      }
    
      @end
    

      dealloc里的析构函数,当对象销毁的时候,会自动调用这个方法,我们在这里重写这个方法。

      在main函数里,写入如下代码:

       

    int main(int argc, const char * argv[])
    
      {
    
        @autoreleasepool {
    
                 Dog *dog = [[Dog alloc] init];
    
         }
    
        NSLog(@"程序即将退出");
    
        return 0;
    
      }
    

      从终端打印信息来看,程序即将退出这条打印之前,已经打印dog dealloc,也就是说在程序运行结束前,dog对象已经销毁了。这个是ARC,由xcode帮我们管理dog对象。

      将ARC改为MRC,再执行程序,dog对象并没有销毁因为我们现在是手动管理了,我们需要遵守内存管理的黄金法则;Dog *dog = [[Dog alloc] init]; 我们需要对dog进行release。

    将main函数代码改为如下形式:

    int main(int argc, const char * argv[])
    
    {
    
        @autoreleasepool {
    
            Dog *dog = [[Dog alloc] init];
    
            [dog release];
    
         }
    
        NSLog(@"程序即将退出");
    
        return 0;
    
    }
    

    再次执行程序,从打印可以看出,dog对象已经销毁。这就是黄金法则,我们对dog进行alloc,就要对dog进行release。

    注意,release 并不是销毁对象,而是让对象的引用计数减1,当对象的引用计数快为0的时候,自动调用dealloc方法并销毁对象。

    3.2 retain与retainCount

    retain,将对象进项保留操作,也就是使对象的引用计数加1。

    retainCount,打印一个对象的引用计数。

    将main函数代码改为如下形式:

    int main(int argc, const char * argv[])
    
      {
    
        @autoreleasepool {
    
            Dog *dog = [[Dog alloc] init];
    
    //此时打印的结果,retainCount值为1,
    
    //也就是我们alloc,创建dog对象时,对象的引用计数为1
    
    NSLog(@"dog retainCount = %lu",[dog retainCount]);
    
     
    
    //dog1指针要使用(引用)dog对象,
    
    //此时,为避免dog对象进行release,
    
    //使得引用计数减1变为0,销毁对象,
    
    //我们进行了retain操作。
    
    Dog *dog1 = [dog retain];
    
    //此时打印的结果,retainCount值为2
    
    NSLog(@"dog retainCount = %lu",[dog retainCount]);
    
    Dog *dog2 = [dog retain];
    
    //此时打印的结果,dog,dog1,dog2,retainCount值都为3,
    
    //因为这三个指针指向同一个对象。
    
    NSLog(@"dog retainCount = %lu",[dog retainCount]);
    
    NSLog(@"dog1 retainCount = %lu",[dog1 retainCount]);
    
    NSLog(@"dog2 retainCount = %lu",[dog2 retainCount]);
    
     
    
     //release 并不是销毁对象,让对象的引用计数减1
    
     [dog release];
    
    //此时打印的结果,dog,dog1,dog2,retainCount值都为2,
    
    //虽然dog执行了release,但dog指针还是指向那个对象。
    
    //此时dog对对象只有使用权,而没有拥有权。
    
     
    
      NSLog(@"dog retainCount = %lu",[dog retainCount]);
    
      NSLog(@"dog1 retainCount = %lu",[dog1 retainCount]);
    
      NSLog(@"dog2 retainCount = %lu",[dog2 retainCount]);
    
      [dog1 release];
    
      [dog2 release];
    
    //执行完上面两句话的时候,dog对象就销毁了。
    
    //虽然这里我们可以写两句[dog release];
    
    //也能达到同样的效果,但是,务必不要这样写,
    
    //我们要遵守内存管理的黄金法则:
    
    Dog *dog = [[Dog alloc] init]; // 这是对dog指针进行alloc,需要对应[dog release];
    
    Dog *dog1 = [dog retain]; //这是对dog1指针进行retain,需要对应[dog1 retain];
    
     
    
    //这时候打印dog的retainCount是错误的用法!!
    
    //因为对象已经销毁了!! 对一个已经销毁的对象发送消息是逻辑错误的!
    
    //会造成程序的崩溃,
    
    //因为dog对象已经销毁了,没法调用dog对象的方法。
    
    //注意,如果上面不加两行打印的话,可能不会崩溃。
    
            NSLog(@"dog retainCount = %lu",[dog retainCount]);
    
         }
    
        NSLog(@"程序即将退出");
    
        return 0;
    
      }
    

    3.3 类的复合中使用

    在上面代码中,增加Person类

    @interface Person : NSObject {
    
      // 一个人,养了一条狗
    
        Dog *_dog;
    
      }
    
      - (void)setDog:(Dog *)dog;
    
      - (Dog *)dog;
    
      @end
    

     setDog方法形式:

       

    @implementation Person
    
      /* 人并没有真正持有狗,
    
    如果在main函数里[dog release],让dog的引用计数减1,就变为0,
    
    dog就销毁了。
    
        - (void)setDog:(Dog *)dog
    
      {
    
        _dog = dog;
    
      }
    
        */
    
     
    
      /* 如果人再持有别的狗,
    
    就会造成第一条狗得不到释放,内存泄露。
    
      - (void)setDog:(Dog *)dog
    
      {
    
        _dog = [dog retain];
    
      }
    
        */
    
     
    
      /* 如果本来持有一条狗,又重新设置这条狗,先进行release,
    
    这个时候,很可能dog就销毁了,然后,就没法再次retain了。
    
      - (void)setDog:(Dog *)dog
    
      {
    
        [_dog release];
    
        _dog = [dog retain];
    
      }
    
        */
    
     
    
      // 标准写法
    
      - (void)setDog:(Dog *)dog
    
      {
               if (_dog  !=  dog) {
    
            [_dog release];
    
            _dog = [dog retain];
    
               }
      }
    
      - (Dog *)dog
    
      {
         return _dog;
      }
    
      - (void)dealloc
      {
       NSLog(@"person dealloc");
    
    // 人在销毁的时候,一并将持有的dog对象销毁
    
        [_dog release];
    
        [super dealloc];
    
      }
    @end
    

     错误方法分析:

       

    //第一个setDog:方法对应的错误
    
              Dog *xiaoBai = [[Dog alloc] init];
    
              Person *xiaoXin = [[Person alloc] init];
    
                 [xiaoXin setDog:xiaoBai];
    
      //引用计数为1
    
                 NSLog(@"count = %lu",xiaoBai.retainCount);
    
                 [xiaoBai release];
    
      //此时狗已经销毁了,因此,xiaoXin需要持有这条狗。
    
                 [xiaoXin release];
    
     
    
     // 第二个setDog:方法对应的错误
    
                   Dog *xiaoBai = [[Dog alloc] init];
    
                    Person *xiaoXin = [[Person alloc] init];
    
                    [xiaoXin setDog:xiaoBai];
    
      //引用计数为2
    
                   NSLog(@"count = %lu",xiaoBai.retainCount);
    
                   [xiaoBai release];
    
                   Dog *xiaoHei = [[Dog alloc] init];
    
                   [xiaoXin setDog:xiaoHei];
    
                   [xiaoHei release];
    
                   [xiaoXin release];
    
      //此时xiaoBai这条狗没有释放
    
     
    
      //第三个setDog:方法对应的错误
    
                   Dog *xiaoBai = [[Dog alloc] init];
    
                    Person *xiaoXin = [[Person alloc] init];
    
                    [xiaoXin setDog:xiaoBai];
    
      //引用计数为2
    
                   NSLog(@"count = %lu",xiaoBai.retainCount);
    
                   [xiaoBai release];
    
      //这样设置是不对的,因为在setDog:里,将dog进行release的时候,
    
      //引用计数为0,dog就销毁了,无法再retain了。
    
                   [xiaoXin setDog:xiaoBai];
    
                   [xiaoXin release];
    
      //另外,这里还要说明,类里,类外,都需要遵守内存管理。
    

     

    3.@property retain,assign,copy展开

    i.) retain展开

    如上代码里,Person的setter和getter方法,也可以用property,写成如下形式:

    @property (nonatomic, retain) Dog *dog;

    进行如上测试,都没有问题。

    因此,实际如果写成这样@property (nonatomic, retain) Dog *dog;,

    则会展开如下:

    - (void)setDog:(Dog *)dog

      {

        if (_dog != dog) {

                 [_dog release];

                 _dog = [dog retain];

          }

      }

      - (Dog *)dog

      {

        return _dog;

      }

    ii.) assign展开

      @property (nonatomic, assign) Dog *dog;,assign是直接复制,

    则会展开如下:

    - (void)setDog:(Dog *)dog

      {

             _dog = dog;

      }

      - (Dog *)dog

      {

         return _dog;

      }

      iii.) copy展开

      @property (nonatomic, copy) Dog *dog;,copy,拷贝,

    将原来的对象拷贝一份出来,展开如下:

    - (void)setDog:(Dog *)dog

      {

        if (_dog != dog) {

              [_dog release];

            _dog = [dog copy];

          }

      }

      - (Dog *)dog

      {

         return _dog;

      }

    3.5 字符串内存管理

      i.) 字符串的内存管理

      对于字符串而言,非常不遵守黄金法则! (如果从字符串的引用计数来看,乱七八糟!)这只是一个表象! 其实内部还是遵循的!!

      我们要做的是,我们依旧遵守我们的黄金法则!

    NSString *str = [[NSString alloc] initWithFormat:@"%d %s",1,"hello"];
    
            NSLog(@"count1 = %lu",str.retainCount);
    
            NSString *str2 = @"hello";
    
             NSLog(@"count2 = %lu",str2.retainCount);
    
            NSString *str3 = [str retain];
    
            NSString *str4 = [str2 retain];
    
            NSLog(@"count3 = %lu",str.retainCount);
    
            NSLog(@"count4 = %lu",str2.retainCount);
    
            NSString *str5 = [[NSString alloc] initWithString:str];
    
            NSString *str6 = [[NSString alloc] initWithString:str2];
    
            NSLog(@"count5 = %lu",str5.retainCount);
    
            NSLog(@"count6 = %lu",str6.retainCount);
    
            // NSString *str7 = [NSString stringWithFormat:@"%d",5];
    
           [str release];
    
           [str3 release];
    
           [str4 release];
    
           [str5 release];
    
           [str6 release];
    
           // str7不用release!!
    

            

    因此,如果是NSString,我们的property格式写成如下: @property (nonatomic, copy) NSString *name;

    ii.) copy和mutableCopy

            

    NSMutableString *string = [[NSMutableString alloc] initWithString:@"hello"];
    
     
    
            // 这样写会崩溃,看对象,不看指针
    
            // copy 将(可变或不可变)字符串拷贝成不可变字符串,string2 实际是不可变字符串
    
            // NSMutableString *string2 = [string copy];
    
     
    
            // [string2 appendString:@"world"];
    
            NSString *string2 = [string copy];
    
            NSLog(@"string2 = %@",string2);
    
     
    
            // mutableCopy 将(可变或不可变)字符串拷贝成可变字符串
    
            NSMutableString *string3 = [string2 mutableCopy];
    
            [string3 appendString:@"world"];
    
            NSLog(@"string3 = %@",string3);
    
     
    
            // 不用管它的引用计数是多少,我们遵守我们自己的黄金法则就够了
    
            [string release];
    
            [string2 release];
    
            [string3 release];
    
            // UI里,也不要随便打印retainCount, 各人顾各人
    
            // new 相当于alloc init,在OC或IOS里几乎不用!!
    

    3.6 数组的内存管理

    int main(int argc, const char * argv[])
    
    {
    
        @autoreleasepool {
    
     
    
            Dog *dog1 = [[Dog alloc] init];
    
            Dog *dog2 = [[Dog alloc] init];
    
            Dog *dog3 = [[Dog alloc] init];
    
            Dog *dog4 = [[Dog alloc] init];
    
            NSLog(@"dog1.retainCount = %lu",dog1.retainCount);
    
            NSMutableArray *array = [NSMutableArray arrayWithObjects:dog1, 
    
    dog2, dog3, dog4, nil];
    
            NSLog(@"dog1.retainCount = %lu",dog1.retainCount);
    
     
    
            [array addObject:dog1];
    
            NSLog(@"dog1.retainCount = %lu",dog1.retainCount);
    
     
    
            // NSLog(@"array = %@",array);
    
            [array removeLastObject];
    
            NSLog(@"dog1.retainCount = %lu",dog1.retainCount);
    
     
    
            // 新的array不是我们alloc new... 的,我们不需要release
    
            // [array release];
    
            NSLog(@"dog1.retainCount = %lu",dog1.retainCount);
    
            [dog1 release];
    
            [dog2 release];
    
            [dog3 release];
    
            [dog4 release];
    
     
    
        }
    
        //for @autoreleasepool
    
        return 0;
    
    }
    

    结论

      1)当我们创建数组的时候,数组会对每个对象进行引用计数加1

      2)当数组销毁的时候,数组会对每个对象进行引用计数减1

      3)当我们给数组添加对象的时候,会对对象进行引用计数加1

      4)当我们给数组删除对象的时候,会对对象进行引用计数减1

      总之,谁污染谁治理,管好自己就可以了。

    3.7 autorelease与 autoreleasepool

    autoreleasepool是一个对象

    autorelease 是一个方法

    在main函数里写如下代码:

    int main(int argc, const char * argv[])
    
      {
    
        @autoreleasepool {
    
          Dog *dog = [[Dog alloc] init];
    
    //dog并没有马上销毁,而是延迟销毁,
    
    //将dog对象的拥有权交给了autoreleasepool
    
        [dog autorelease];
    
    //这个是可以打印的,因为打印完dog的引用计数后,
    
    //dog对象才销毁
    
    @autoreleasepool{
    
         [dog autorelease]
    
    }
    
        NSLog(@"retainCount = %lu",dog.retainCount);
    
         }
    
        NSLog(@"程序即将退出");
    
        return 0;
    
      }
    

     注意: autoreleasepool相当于一个数组,如果哪个对象发送autorelease消息,实际将对象的拥有权交给了autoreleasepool;当autoreleasepool销毁的时候,将向autoreleasepool里持有的所有对象都发送一个release消息。

    3.8 加方法的内存管理 

      我们用加方法创建的对象,不用我们release,是因为类内部的实现使用了autorelease,延迟释放。

      在Dog类的声明里增加一个加方法

      + (id)dog;

      在Dog类的实现里进行实现

      + (id)dog

      {

    /*注意,这里不要写成release,如果是release,那么刚创建就销毁了,

    使用autorelease,使得将对象的拥有权交给了自动释放池,

    只要自动释放池没有销毁,dog对象也就不会销毁。*/

    return [[[Dog alloc] init] autorelease];

      }

      在main函数里代码如下:

       

    int main(int argc, const char * argv[])
    
      {
    
        @autoreleasepool {
    
                 Dog *dog = [[[Dog alloc] init] autorelease];
    
                 Dog *dog1 = [Dog dog];
    
                 NSLog(@"count = %lu",dog.retainCount);
    
                 NSLog(@"count1 = %lu",dog1.retainCount);
    
          }
    
      }
    
  • 相关阅读:
    centos 用户管理
    rsync 实验
    文件共享和传输
    PAT 1109 Group Photo
    PAT 1108 Finding Average
    PAT 1107 Social Clusters
    PAT 1106 Lowest Price in Supply Chain
    PAT 1105 Spiral Matrix
    PAT 1104 Sum of Number Segments
    PAT 1103 Integer Factorization
  • 原文地址:https://www.cnblogs.com/sublimter/p/4162973.html
Copyright © 2011-2022 走看看