• iOS面试_1.浅析内存管理


      为了开学的面试,就在博客里总结一下面试会问到的问题,今天就来谈谈内存管理,看到一篇文章非常不错,http://vinceyuan.cnblogs.com/,深入浅出,推荐大家去看看!

      Objective-C使用一种(Retain Count)引用计数的机制来管理内存,在OC中,每个对象都持有自己的retain count,引用计数可以理解为就是一个计数器,当对象alloc创建的时候,会自动设置为1,当给对象发送retain消息的时候,引用计数会加1,当给对象发送release消息的时候,引用计数会减1,当引用计数为0的时候,对象会释放所占用的内存,这就是内存管理的机制,听起来比较容易吧,下面就进一步分析这种机制。

      首先,我们应该知道为什么要这样做?我们经常在不同的对象中引用相同的对象,例如:假设我们用不同电脑远程连接到同一台服务器进行远程操作,转化成OC语言就是不同电脑对象引用相同的服务器对象,这时候有一台电脑在服务器上敲了shutdown命令,让服务器挂掉了,这时候我们所有电脑都连接不上去了,其他人就工作不了了,引用计数其实就像一个计数开关,只有当没有电脑连接的时候,计数为0,才允许执行shutdown命令(非常理想状态下,现实中可不要这样做)。

      不同于java的GC回收机制,java中当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。OC中这种特有的retain count机制,给我们更多权限,让开发者去控制对象释放的时间以及如何去释放,所以我们得更加小心,过早的释放内存,可能会引起程序崩溃,长时间不释放占用的内存,程序在运行一段时间后可能会发生内存泄露。

       Objective-C采用了引用计数(retain count)。对象的内部保存一个数字,表示被引用的次数。例如,某个对象被两个指针所指向(引用)那么它的retain count2。需要销毁对象的时候,不直接调用dealloc,而是调用releaserelease会让retain count1,只有retain count等于0,系统才会调用dealloc真正销毁这个对象。

            ClassA *obj1 = [[ClassA alloc] init];//对象生成时,retain count = 1
            
            [obj1 release]; //release使retain count减1,retain count = 0,dealloc自动被调用,对象被销毁
            

     下面观看一个例子

    ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1
    ClassA *obj2 = obj1; //retain count = 1
    [obj1 hello]; //输出hello
    [obj1 release]; //retain count = 0,对象被销毁
    [obj2 hello];
    [obj2 release];

    obj2引用了obj1,此时retain count为1,当obj1执行完消息释放后,retain count=0,此时obj2变成了无效指针,这里再执行[obj2 release]会引起内存的过度释放

    所以一定要谨记,不是alloc创建,而是指针赋值的时候,一定要retain,拿到对象的所有权

    ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1
    ClassA *obj2 = obj1; //retain count = 1
    [obj2 retain]; //retain count = 2
    [obj1 hello]; //输出hello
    [obj1 release]; //retain count = 2 – 1 = 1
    [obj2 hello]; //输出hello
    [obj2 release]; //retain count = 0,对象被销毁

    这样写的确可以解决问题,但是如果对象非常多得时候,这样的操作会不会太繁琐了点,有没有简单一点的解决办法?所以oc引入了自动释放池autorelease pool,这也不同于java的全自动垃圾回收

    新生成的对象调用autorelease就可以了

    ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1 但无需调用release

    如果存在指针赋值,与上面的代码也相似

    ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1
    ClassA *obj2 = obj1; //retain count = 1
    [obj2 retain]; //retain count = 2
    [obj1 hello]; //输出hello
    //对于obj1,无需调用(实际上不能调用)release
    [obj2 hello]; //输出hello
    [obj2 release]; //retain count = 2-1 = 1

    这里有个有趣的问题,retain count不是1么,还不能销毁呀,什么时候才能销毁呢?

    所以我们得了解一下autorelease pool的原理机制。

    1)autorelease pool不是天生的,需要手动创立。只不过在新建一个iphone项目时,xcode会自动帮你写好。autorelease pool的真名是NSAutoreleasePool

      

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    2)NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声明为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。

    ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此对象加入autorelease pool中

    3)   NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数组,release数组中的每个成员。如果此时数组中成员的retain count1,那么release之后,retain count0,对象正式被销毁。如果此时数组中成员的retain count大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。

    那是不是有了自动释放池autorelease pool就万无一失了,其实不然

    默认只有一个自动释放池

    int main (int argc, const char *argv[])
    {
    NSAutoreleasePool *pool;
    pool = [[NSAutoreleasePool alloc] init];
     
    // do something
     
    [pool release];
    return (0);
    } // main

    所有标记为autorelease的对象在这个pool内被销毁,但是如果这个自动释放池里面含有大量autorelease的对象,还是容易造成内存不足的情况,比如说:

    int main (int argc, const char *argv[])
    {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    int i, j;
    for (i = 0; i < 100; i++ )
    {
     for (j = 0; j < 100000; j++ )
        [NSString stringWithFormat:@"1234567890"];//产生的对象是autorelease的。
    }
    [pool release];
    return (0);
    } // main

    这种情况,大量内存被占用,只有poll销毁的时候,那些声明为autorelease对象才被销毁,这对于ios程序来说并不乐观,iphone内存本身有限,那有没有更好的解决办法,所以我们可以用autorelease嵌套机制来控制。

      Objective-C程序中可以嵌套创建多个autorelease pool。在需要大量创建局部变量的时候,可以创建内嵌的autorelease pool来及时释放内存。

    int main (int argc, const char *argv[])
    {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    int i, j;
    for (i = 0; i < 100; i++ )
    {
     NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
     for (j = 0; j < 100000; j++ )
        [NSString stringWithFormat:@"1234567890"];//产生的对象是autorelease的。
     [loopPool release];
    }
    [pool release];
    return (0);
    } // main
  • 相关阅读:
    CentOS6.5安装Scrapy
    CentOS6.5安装pip
    CentOS6.5 安装openssl
    curl不能支持https问题
    pip安装时遇到的问题集锦,持续更新!
    CentOS6.5安装python3.7
    IntelliJ IDEA 17 本地LicenseServer激活
    omnidb数据库web管理工具安装
    CentOS7安装Kubernetes1.18.1并使用flannel
    Portainer中文汉化
  • 原文地址:https://www.cnblogs.com/iOS-dd/p/3279291.html
Copyright © 2020-2023  润新知