内存管理

内存管理的原理?

内存空间总共有8块区域,有两个区域需要特别注意,一个就是堆空间,一个就是栈空间。栈里存放临时变量(对象的指针变量,以及整形和浮点型),堆里存放动态变量(对象的存放)对象被引用次数,引用计数器。在堆的存储对象里,每个对象内部都会分配4个字节的存储空间来存储引用计数器。alloc和new都会开辟内存空间,只要计数器不为0,就会占用内存空间。retain,对象计数器加1。release,对象计数器减1。retainCount返回当前对象引用计数值。dealloc在对象被销毁时会被调用。严谨就严谨在这儿,时候时候才能算作对象被销毁呢?就是引用计数变为0的时候。这时候dealloc里必须写的一个方法就是,给被销对象里的属性赋值的如果是一个对象的时候,那么这时候也需要把这个被销毁的属性对象给realease掉。当然这个时候也道出了dealloc的本质:重写父类的dealloc,对象被销毁都会调用dealloc方法,即对象死之前再说点遗嘱,做点事情。既然是重写父类方法,其实准确地说是增添父类的方法,那就必须调用【super dealloc】,因为super里面也要做一些回收操作,否则无法调用父类里的dealloc方法。而且一定要放在最后面。植物大战僵尸的子弹内存回收就是一个典型的手动内存回收机制。

僵尸对象?

所占用内存已经被回收的对象,人死也不能复生,僵尸对象也不能起死回生后再次使用,也就是说对象成为僵尸对象之后,任何消息都不能发送给这个对象,即使是retain那也不行。野指针?简单的说就是指向僵尸对象(不可用内存)的指针。栈内存空间里总会有一个指针变量指向堆内存空间里的对象。至于这个对象现在是否还拥有自己的内存空间并不影响指针变量去指向它,当操作对象的指针的引用计数为0时,堆空间的对象内存空间已经消失,但是指向对象的线却依然存在,也就是说对象的内存空间已经不存在,指针却依然存在,不为空,也就是指针变量依然存储着指向堆内存空间里的对象的那个内存地址,也就是我们通常意义上理解的那一个”箭头“。去访问对象的这片这个内存空间,自然崩溃!只有将指针变量指向nil,也就是将指针变量指向堆内对象的那篇内存空间的那个箭头给注销掉。也就相当于清除了指针指向对象内存的那一条线。这样就不会错误了。OC中不存在空指针错误,给空指针发送消息,不报错。retain返回对象本省。release没有返回值。空指针?没有指向任何东西的指针(存储的东西是nil,NULL,0),给空指针发送消息不会出错。

两个对象之间有关系的时候,就是内存管理还是很严谨的!

何处此言,就是说!先说一下我经常出现的一个错误,就是当我使用全局变量接收实例化的一个对象的时候,总是不会加retain,当然这个地方有一点不一样的就是。这里时全局变量,当然全局变量是写在.h还是写在.m里是有区别的,前者可以在非本类被使用,所谓使用就是赋值和取值这两种嘛!后者则主要是为了私有,只是在本类里使用。MJ视频主要是想说明?两个对象之间有联系的情况,比如说对象1拥有对象2类型的属性,那么这中间的内存管理该如何去管理,当然肯定在实例化对象1和实例化对象2的时候就一定会使得各自的引用计数变为1.那么问题来了,如果我给对象1的属性通过set方法赋值。

set方法赋值的输入参数到底是什么?

其实输入参数到底是什么不最终还是取决于全局变量属性时声明属性变量的那个类嘛!既然现在声明属性变量的类时是那个对象2的类,那自然就是将通过对象2的类实例化的对象2作为输入参数作为对象1的set方法的输入参数嘛!现在的问题就是,现在总共有两个指针指向对象2,但是引用计数任然为1,也就是说,当我们注销对象2时,对象2立马消失,这样有一个指针却仍然指向这片内存空间,但是内存空间已经被释放不存在了,这样那个通过set输入参数建立引用的指针就是我们常说的野指针。这个野指针指向的那个内存空间已经被释放的对象自然也就是僵尸对象了!

问题关键如何保证对象在有指针指向它的时候不会被销毁呢?

解决方案就是只要有一个引用,或者说只要有一个指针指向这个对象,那么就让这个对象的引用计数加1�。这里需要注意最根本的一点就是不仅仅是只有alloc和new才会创建指针变量指向对象,其实将对象通过set方法赋值给另一个对象的属性时也会在栈内存空间生成一个指针变量。只是我不明白的是,这个指针变量是什么呢?毫无疑问,当然就是对象1建立属性的时候创建的那个属性变量名了!现在知道为什么创建对象属性的时候需要添加号,而创建整型属性变量的时候不用添加号了吧!其实准确的解释也不是太懂,但是至少整型这类属性变量是存储在占空间,如果属性变量名表示指针,而单独不要的,前面加_下划线的属性变量名就是代表了真正的属性,那么我想说的就是整型类型的属性不需要指针,因为它们是存储在栈空间,只有存储在堆空间的对象才需要指针专门记录对象在堆空间中的具体位置,并将地址列表存储在栈内存空间里,也即是说,只有对象类型的属性变量,存储在堆内存空间的变量才需要添加号用来记录对象在堆内存空间的地址。既然是将对象2赋值给一个带的属性变量,自然就相当于指针指向了对象2。既然是有一个指针指向了对象2,那么必须要做的一件事情就是将对象2的引用计数加1。于是就使用到了retain了,现在又回归一个经常出现的问题就是,我总是忘记当把实例化的对象赋值给全局变量的指针的时候需要retain了吧?逻辑就是:1、创建的全局变量就是一个带号的对象2、把一个实例化的对象赋值给带*号的全局变量的时候自然就相当于创建了一个指针指向了对象。3、既然是引用,自然就需要采用retain将引用计数加1。那么现在就是一个问题了,如何在对象1被注销的时候,自然对象1里面的指针属性也会消失,但是在指针属性消失前一定要做的一件事情就是要做一个负责任的指针,就是在指针消失之前,通过指针里面存储的位置找到对象的内存空间,如何释放掉这个内存呢?当然是通过下划线_属性变量名称调用realease方法将指针指向的对象的引用计数减一,也就是去掉那一个箭头符号。当然还有一种我未曾遇到过的问题就是,当我已经实例化了一个对象2并通过set方法赋值给了对象1的指针属性变量,而且现在对象2的引用计数变成了2,可是我如果再实例化一个对象2并且也是通过set方法将对象2赋值给对象1的属性!那么内存管理有回出现一个问题,什么问题自己去发现,不过解决方案就是在set赋值方法里首先调用释放一次当前指针所值对象的引用计数再进行赋值增加引用计数!这里的方法使用的前提就是,必须先判断通过set方法的输入参数传输进来的对象2是否不同于正在使用的对象2。只有判断两个对象2不同,才进行旧对象release,新对象的retain方法。

我后面说的情况其实是非常常见的一种情况,就是当一个对象引用另一个对象的时候,自然是通过对象1的属性类型是对象2的形式,通过set方法赋值的时候,当然仅仅是更改属性里的值时那么简单就好了,也就是,先对比一下基础类型变量,就是如果对象1的属性是一个基础类型变量,第一次直接通过set方法给基础类型变量的数据直接进行复制,如果我现在需要更改基础类型数据属性的值只需要重新调用set方法即可赋值更改成功。第一次的基础类型变脸存储的数据自动被第二次重新赋值给基础类型数据的值所覆盖。但是,要知道,基础类型数据可不同于对象类型数据,前者是存储在栈内存空间,而后者是存储在堆内存空间。两者是有区别的,前者的set方法就是一个纯粹的简简单单的set方法赋值罢了!而后者也就是对象属性在更改属性值得时候,如何才能考虑到内存管理相关的问题呢?首先我们知道基础类型属性存储数据是不需要开辟内存空间的,而对象类型的数据是不仅需要在堆内存空间里开辟空间,更需要在栈内存空间里存储对象类型的内存的路径的指针数据。而且这不是关键,关键在于为了管理对象属性的内存空间的释放的问题,就引入了引用计数这个概念。所以再覆盖掉原来的值的时候是必须保持引用计数不会发生改变。那如何才能实现这一点了,只有在set方法里进行判断,新传入的对象属性值是否与已经存在的对象属性值一样,如果一样,自然不进入if判断,就相当于set方法不会被执行,反正被执行了效果也是一样的。更常见的情况就是新传入的对象属性值与已经存在的对象属性值不一样。那么自然需要进入if判断里面的方法,这里面有一句与基础类型数据相同的一句话就是给对象属性重新赋值的一句话。但是与基础类型数据赋值不同的就是对象类型数据每一次赋值的同时都需要增加被赋值对象的引用计数,你可能回说,这样岂不是造成被赋值的对象的引用计数不断累加,最终销毁不了,所有为了防止这种错误,最必须的一个方法就是每一次再覆盖对象属性已经存在的值之前都必须考虑到,就的对象类型数据的值得引用计数的减一,只有先通过减一再通过加一自然才会保证对象被引用一次时引用计数只会加一。苹果认为这样写很麻烦呀!每对一个类的对象开辟一个全局变量属性,要想做好内存管理都必须写好set方法和dealloc方法。而且我们上文已经提到过每一个对象从被创建到最终被销毁的时候,创建时要注意set方法,而当对象快要被销毁的时候则又需要格外注意dealloc方法。只要调用了alloc,必须有release或autorelease。

属性为什么要加修饰符,不加修饰符会发生什么样的情况?

就是苹果认为每创建一个全局变量都需要考虑set方法和get方法。为什么不考虑一下封装呢?给全局变量的创建封装成一个方法,通过传入一些参数就自动生成不同类型的get方法和set方法。创建全局变量的高级封装方法就出来了。就是属性!

属性自动帮我们生成get方法和set方法。但是set方法里面有判断,默认的属性生成的get和set方法是很笨的。就是最简单的那种纯粹赋值的set方法!通过给创建的属性一些修或者说一些参数就可字自动生成我们想要的set方法和get方法。需要注意的是:不能又写属性又写set方法和get方法。否则会报错。当我们什么都不写的时候,默认就是assign也就是生成最常见的哪一种set和get方法。这种方法就是当我们创建的全局变量是基础类型数据不需要开辟内存,自然更不需要考虑通过使用引用计数来释放内存的时候,使用默认的属性就行又或者使用assign就行。当我们需要创建对象属性的set方法和get方法的时候,难就难在对象类型全局变量需要在堆内存空间动态开辟内存,因此需要引用计数这个概念来管理对象类型的内存应该合适被释放。所以set方法里必须有一个计数器加减的过程,前面有介绍,那么如何通过限制修饰属性来创建这样的set方法呢?自然是通过retain来进行修饰。如果给属性加上retain修饰法,就会生成带判断的set方法。也就是说现在set方法里有retain方法。相当于会释放掉就对象,重新将新对象赋值给属性对象指针,且保持引用计数不变。但是依然要记得在dealloc方法里面写上被释放对象里相关的对象,要负责任嘛!retain的本质就是release旧值 retain新值,主要适用于OC对象类型。assign本质就是纯粹的set方法直接赋值,而且你会发现如果属性什么修饰符也不写,默认的set方法就是直接赋值,可以看出assign就是赋值的意思,根本对于那种基本数据类型只是需要set方法单纯赋值的功能的来说根本就是可写可不写嘛!主要使用于非OC对象类型。copy 的本质就是release旧值,copy新值。其实我一直不明白的就是,为什么我在自动内存管理情况下建立数据模型的时候,必须使用copy而不是retain来修饰OC对象类型呢?

除了可以通过修饰属性来决定生成哪一种set方法以外,还可以通过readonly ——同时生成setter和getter的声明和实现。readwrite——只会生成getter的声明、实现。本质就是是否要生成get方法。只读表示外界只可以读取到对象的数据,表示对象只会生成get方法而已!多线程:nonatomic——性能高,不写这个修饰会影响性能,必须写。atomic——性能低(缺省),默认的就是这个。

循环引用?

类与类之间的关系,即你引用我,我引用你。我包含你,你包含我。对象用retain,基础数据类型用assign.类1包含类2的头文件,类2包含类1的头文件。这样会错,所以不能使用#import(意味着把类里面的所有方法都导过去),应该使用@class(仅仅告诉编译器,某个名称是一个类名)。使用@class可以办到#import不能办到的循环引用!解决方案就是,一个类用retain,另一个类使用assign。A对象retain了B对象,B对象retainA对象。这样会导致A对象和B对象永远无法释放!当两端互相引用时,应该一段使用retain,另一端使用assign。而且使用@class个类可以极大地提高编译器的效率,假设100个类的.h文件都使用#import方法来引用了类1,那么带来的问题就是只要类1稍微改变一点,这100个类都会重新编译,当使用@class则可以完美避免这个问题,完美提升编译效率。ARC中的循环引用问题?两个类之间,你strong我,我strong你。(人养了条狗,狗的主人有个人)解决方案就是一个strong,一个用weak.两端weak和两端strong都不行,前者就是可能成员变量还在,对象却消失了,后者则是无法销毁对象。所以weak一般就用在这种场合。

autorelease ?

本质半自动释放,本质延迟对象被销毁时的时间,当写完autorelease不会马上执行对象的release方法,只是表示将对象扔到自动释放池中,必须等到自动释放池被销毁时,当初写的autorelease才会生效。

不用担心对象被release的代码与其他代码的顺序,也即是不用担心对象什么时候会被释放,适用于占用内存比较小得对象。但是缺点不够精确,比如游戏的子弹必须在超出屏幕之后进行内存释放。

开启僵尸对象,release没有返回值,autorelease是有类型为id 的返回值的,因此可以在初始化的时候进行连着写。只要对象调用了这个方法,就会把对象扔到了自动释放池!当自动对象池被销毁时,会对池子里的面的所有对象做一次release操作,记住仅仅是release操作哈!不一定代表自动释放池被销毁的时候,里面的的对象就一定会被销毁!

为了避免多次使用autorelease方法,因此可以将所有创建对象的过程完完整整的放进自动释放池里。@autoreleasepool{};的中括号里面就写创建对象的过程。在@autoreleasepool{};括号里开辟对象的时候再使用autorelease方法时并不会使得对象的引用计数器发生改变。改变引用计数只能使用retain或alloc。@autoreleasepool{}是存储在栈内存空间里

用法:

1、会将对象放到一个自动释放池中

2、当自动释放池被销毁时,会对池子里面的所有对象做一次release操作

3、会返回对象本身

4、调用完autorelease方法后,对象的计数器不变

好处:

1、不用再关心对象释放的时间

2、不用关心什么时候调用release

注意:

1、占用内存较大的对象不要随便autorelease,因为要等到很久才会释放对象的内存,很占内存空间的。而且一旦使用自动释放池就不能精确控制对象释放时间

自动释放池:

1、在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在

2、当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池(也就是包括它的对象池)

dealloc是系统自动调用的。

非OC类型对象:(int,float,enum,struct)

特别注意OC对象里的所有全局变量都会有类名号。不要以为id是个例外,要知道id本身就包含一个号。

只要有retain就必须dealloc方法里调用release.编译器特性,插入那个地方需要插入realease代码。ARC自动内存管理禁止调用realease方法。

ARC原理就是通过编译器自动判断再说么地方应该插入对象的release方法。也即是说本质就是:ARC就在适当的地方插入release或atorelease。而且必须强调一点就是,指针变量用来操作对象,假设指针变脸是P,那么P代表了指针变量里的内容,也就是对象存储在堆内存空间里的地址。_p代表了成员变量,可问题在于,成员变量_p到底与指针变量p是什么区别呀?我当然知道一个有 号,另一个没有号。通过操作指针变量就可以对指针所指向的对象进行操作,通过调用成员变量就可以进行set赋值和get取值操作。而且突然发现好像在控制器里实例化的对象和在成员变量或属性变量修饰之区别,前者直接指向了一个具体的对象。而后者只能算作是某一类型OC对象的成员变量,那么问题来了,操作控制器里实例化的对象的时候,通常来说是通过直接调用指针变量来操作所指向的对象,包括在MRC里改变引用计数。以及使用指针变量调用这个对象所具有的成员变量或属性。成员变量和属性差别还挺大呢?在MRC里,要想通过指针变量操作成员变量,还必须通过已经声明和实现的set方法和get方法。自从有了属性之后,才又出现了self的点语法。那么属性和最初的成员变量的差别在哪儿呢?确实成员变量加上set方法和get方法等于属性。所以前者只能通过_p调用set和get方法才能进行复制或取值。可是问题就在为什么必须在成员变量名前加一个下划线呢?难道是为了区分这表这个对象属于对象引用的属性变量而不是控制器创建的对象么?而且属性也是同样的道理。加上下划线和使用self的点语法都是可以的。我能理解就是既然是成员变量自然需要与控制器里面创建的对象进行区分。现在弄清楚了指针变量(p),成员变量(_p),和指针(p)。现在就可以进一步阐述ARC了。在ARC中,允许重写dealloc,但是不允许调用[super dealloc]方法。在ARC中需要引入两个概念就是:强指针——尽管不是我自己创建的对象,也可以进行强引用,MRC中使用retain,ARC中使用strong。弱指针——弱指针不能决定对象是否被释放。而且弱指针指向的对象的不存在时,系统会自动清空弱指针。怎么写都是对的,不不会发生野指针错误。如果对象一开始就是一个弱指针(__weak),那么就会造成对象一创建出来就会被销毁。因为没有强指针(__strong)指向对象。只要没有强指针(默认情况下所有的指针都是强指针)执行对象,就会释放对象,当main函数执行完成之后或者当我们手动将指针变量滞空会后,指针变量也会被清空销毁,因此这时候的强指针就会消失。自然编译器就会自动在指针变量消失之后加上release方法。用ARC之后就怎么写都是对的。

ARC与MRC的区别或好处在于?

ARC中除了retain改成strong,其它一切不变。而且整个工程从直接像便魔术般将MRC改成ARC(Edit——Refactor——Convert to Objective-C ARC)。ARC的好处就在于再也不用关心什么时候使用realease.而且无论怎么写都不会错。ARC里面如果一个对象要强引用另外一个对象,不使用retain,而是用strong ,原因就在于:strong代表强引用,只要强指针还指着创建的OC对象类型属性。就意味着我们的成员变量是一个强指针。也就是意味着我这个成员变量(下划线指针变量名)在,你这个对象就在。只需记住将以前的retain换成strong就行。当成员变量使用强指针,自然使得成员变量指向的对象是一个强指针,这样即使当初创建被指向对象的指针被滞空,也就是说消失一个强指针依然不会造成对象的销毁,因为另一个对象里的成员变量依然创建了一个强指针指向这个对象,就是刚才那句话,只要我这个成员变量还在,你那个对象就消失不了。这就是strong的作用,试想过去,我们使用retain就表示我们对被指向对象做一次retain操作,就以为着我们拥有那个对象啦!对象都被销毁了,对象的成员变量当然不可能存在啦!成员变量都不在了,自然通过成员变量创建的指向另一个对象的强指针也不会存在啦!强指针都不存在啦,自然当初被指向的对象也不会存在啦!注意一点:当使用strong或weak修饰属性的时候,是不需要在strong或weak前面添加双下划线_的。

在ARC中什么时候使用strong,又什么时候使用weak,又什么时候使用assign来修饰属性呢?

strong 和weak都智能修饰OC对象类型的属性。前者主要为成员变量创建一个强指针,而后者则为成员变量创建了一个弱指针,弱指针对于避免循环引用非常实用。对于非OC对象类型的成员变量的修饰与当初MRC一致,使用assign进行修饰。

推荐阅读更多精彩内容

  • 内存管理是程序在运行时分配内存、使用内存,并在程序完成时释放内存的过程。在Objective-C中,也被看作是在众...
    蹲瓜阅读 1,161评论 1 7
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,085评论 1 15
  • 内存管理概述 内存管理内存的作用:存储数据. 如何将数据存储到内存之中.声明1个变量.然后将数据存储进去. 当数据...
    指尖书法阅读 499评论 2 7
  • 内存管理的基本范围和概念. 程序运行过程中药创建大量的对象, 和其他高级语言类似,在ObjC中对象存储在堆区,程序...
    ValienZh阅读 294评论 0 1
  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 518评论 0 2