• 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。不能多次释放。假设多次释放,则会崩溃。

    可是,假设项目比較复杂,须要有几十上百号人一起分工完毕,就非常easy出现故障。

    例如说我们开辟了一块内存空间。里面存放了一块非常实用的数据。可是,这个数据不仅仅有我在这一块代码里用,甚至有多个人,在程序的多个地方使用。这样造成的结果就是,就算我使用完毕这块内存,我也不能去释放他,由于我不能确定,别人在别的地方是否还须要使用这块内存。内存泄露在所难免了。

     

    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 怎样将project改为MRC

    xcode5。project创建的时候是ARC的。我们假设想要MRC,须要进行例如以下设置。

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

     

     

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

     

    3.1alloc与release

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

    创建一个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);
    
          }
    
      }
    

     

  • 相关阅读:
    2015年北京大学软件project学科优秀大学生夏令营上机考试---C:单词翻转面试题
    跟我学Java多线程——线程池与堵塞队列
    Swift学习——类的定义,使用,继承,构造等(五)
    LNMP编译安装(centos7+nginx1.9+mysql5.6+php5.5)
    【iOS开发系列】九宫格布局
    出现异常时直接把e输出比输出e.getMessage()好得多
    往服务器上传个文件只要不到10毫秒,往数据库写条记录却要10秒
    使用struts的logic:iterate标签遍历列表时得到显示序号
    一次性上传多个文件到服务器端(一)
    Another MySQL daemon already running with the same unix socket的解决
  • 原文地址:https://www.cnblogs.com/cxchanpin/p/6973355.html
Copyright © 2020-2023  润新知