• Objective


    前言

    在我们OC中, 有一个东西是重中之重的知识点, 那就是内存管理, 什么是内存管理呢? 其实内存管理是我们app在运行的时候所使用的内存大小, 在iOS中, 给我们应用设定了一个固定的内存值, 一旦超过这个值, 系统就会给app发送内存警告, 如果这个警告不处理, 那么就会强制关闭应用, 就是我们所说的闪退, 那么内存有多少种呢? 内存其实是分为五种, 分别为堆, 栈, 全局区, 文字常量区, 程序代码区, 在实际开发中, 我们关注的最多的是堆和栈, 其余三个有兴趣的朋友自行去了解.



    开始

    在我们没有学习OC内存管理机制的时候, 我们来看一个例子, 看看我们之前所写的代码是怎么样管理内存的:

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    @end
    
    @implementation Person
    @end
    
    int main()
    {
        int a = 1;
    
        int b = 2;
    
        Person *p = [[Person alloc]init];
    	
        return 0;
    }


    示意图




    在例子里面, 我们知道a, b, *p都是局部变量, 一旦所在的代码块执行完之后就会消失不见, 但Person对象并不会不见, 而是会一直存在于内存中, 我们都知道OC一开始的时候就会加载所有的类, 给它们分配存储空间, 而这个存储空间就是堆, 那么我们怎么去释放这一块内存空间呢? 其实说到底就是调用一个方法, 给Person这个类发送一条消息, 让它自己释放, 那就可以解决我们的需求.




    在OC中, 这种内存管理方式称为计数器, 而存在于堆里面的叫做引用计数, 如果要让堆里面的空间被释放, 唯一的办法就是让引用计数变为0, 这样子系统就会自动回收引用计数为0的类, 其实这样子也就是, 只要引用计数不为0的类, 就会一直存在于内存中, 不会被释放.




    那么在什么情况下才会产生引用计数呢? 其实在我们一刚开始创建对象的时候引用计数就会产生, 也就是alloc,new, 还有一个我们暂时没有学到的copy, 所产生的引用计数默认为1, 而这个引用计数的类型是int类型, 大小为4.



    谁再去调用, 那么引用计数就会+1, 谁释放就会-1, 以此类推.


    PS: 这里的+1就是给对象发送retain消息, 而发送release那么引用计数就会-1, 发送retainCount消息, 就会返回当前引用计数的数值.




    那还有没有更加简单直接的方式知道对象被释放了呢, 其实在对象被释放的时候, 系统会自动调用一个叫做dealloc的方法, 而我们可以重写dealloc方法, 就可以知道对象是否有没有被释放.



    下面让我们一起来探讨一下吧:

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    @end
    
    @implementation Person
    - (void)dealloc
    {
        NSLog(@"对象被回收了");
        [super dealloc];
    }
    @end
    
    int main()
    {
        Person *p = [[Person alloc]init];
    
        NSUIntger a = [p retainCount];
    
        NSLog(@"a = %d", a);
    
        [p release];
    
        return 0;
    }


    打印出来的结果是:

    2015-01-25 11:55:45.619 1.引用计数器的基本操作[2092:163711] a = 1
    2015-01-25 11:55:45.620 1.引用计数器的基本操作[2092:163711] 对象被回收了

    PS:[super dealloc];这句代码一定要卸载dealloc方法的最后面, 否则就会出错, 原理就是先释放自己, 然后再释放父类.




    这里说一下, 其实release并不是释放对象的意思, 而是引用计数减一, 如果你使用了alloc 又使用了retain, 那么引用计数就是2, 写一个release是不能释放的, 必须得有两个, 直到引用计数为0之后才会释放对象, 有增就必须得有减.




    那有人会突发奇想, 既然是这样子, 那我就多写几个release吧, 这样也是不对的, 下面让我们来看看:

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    @end
    
    @implementation Person
    - (void)dealloc
    {
    	NSLog(@"对象被回收了");
    	[super dealloc];
    }
    @end
    
    int main()
    {
    	Person *p = [[Person alloc]init];
    
    	NSUIntger a = [p retainCount];
    
    	NSLog(@"a = %d", a);
    
    	[p release];
    
    	[p release];
    	
    	return 0;
    }

    如果是这样子写, 那么程序在运行中就会报错, 报下面这个错误:(一般我们称为野指针错误)

    EXC_BAD_ACCESS(code=1, address=0x18)
    




    为什么会报这个错? 其实原理很简单, 在OC中, 一个引用计数变为0的对象, 我们称为僵尸对象, 就是说内存不可用的对象, 而指向僵尸对象的指针我们称为野指针, 当对象被释放掉的时候, 只要指针的地址不清零, 那么指针和对象的关系就不会消失, 所以一旦当我们给一个内存不可用的对象继续release, 那么软件就会崩溃, 而在后面的所有代码都不能运行.




    所以在编程的时候, 我们要注意引用计数的增减是否对应, 一旦引用计数不为0, 那么对象就会不被释放, 如果引用计数为0之后还继续release, 那么就会报错, 这个我们需要注意一下.



    如果想解决这个问题, 有两种方法, 一种是删掉多余的release, 另一种是把指针变成空指针, 在OC中, 给空指针做操作是不会报错的, 只是会有一个警告而已, 比如:

    int main(int argc, const char * argv[])
    {
        Person *p = [[Person alloc]init];
        
        NSUInteger a = [p retainCount];
        
        NSLog(@"a = %ld", a);
        
        [p release];
        
        p = nil;
        
        [p release];
        
        return 0;
    }
    

    结果:

    2015-01-25 13:40:26.188 1.引用计数器的基本操作[2251:185241] a = 1
    2015-01-25 13:40:26.189 1.引用计数器的基本操作[2251:185241] 对象被回收了
    

    还是和我们之前得到的结果一样~~~



    总结一下:

    1.方法的基本使用

    1> retain :计数器+1,会返回对象本身

    2> release :计数器-1,没有返回值

    3> retainCount :获取当前的计数器

    4> dealloc

    * 当一个对象要被回收的时候,就会调用

    * 一定要调用[super dealloc],这句调用要放在最后面

     

    2.概念

    1> 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用

    2> 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS

    3> 空指针 :没有指向任何东西的指针(存储的东西是nilNULL0),给空指针发送消息不会报错

     






    好了这次我们就讲到这里, 下次我们继续~~~

  • 相关阅读:
    多线程篇七:通过Callable和Future获取线程池中单个务完成后的结果
    多线程篇六:线程池
    微服务学习和认识
    多线程篇五:多个线程访问共享对象和数据的方式
    多线程篇四:ThreadLocal实现线程范围内变量共享
    多线程篇三:线程同步
    多线程篇二:定时任务
    多线程篇一:传统线程实现方式
    Jms学习篇二:ActiveMQ
    04-运算符
  • 原文地址:https://www.cnblogs.com/iOSCain/p/4282830.html
Copyright © 2020-2023  润新知