浅谈iOS内存管理机制

字数 373阅读 34

对于我们直接接触ARC的开发者来说很难体会到内存这一概念, 以前工作的时候也基本不关心这个感念 ,最近工作发现自己出现了很多错误, 发现对内存这一概念理解的太浅薄!!
下面我对内存这一块做了一些总结
下面我成列一下常规的内存这一块

iOS内存管理机制的原理是引用计数,引用计数简单来说就是统计一块内存的所有权,当这块内存被创建出来的时候,它的引用计数从0增加到1,表示有一个对象或指针持有这块内存,拥有这块内存的所有权,如果这时候有另外一个对象或指针指向这块内存,那么为了表示这个后来的对象或指针对这块内存的所有权,引用计数加1变为2,之后若有一个对象或指针不再指向这块内存时,引用计数减1,表示这个对象或指针不再拥有这块内存的所有权,当一块内存的引用计数变为0,表示没有任何对象或指针持有这块内存,系统便会立刻释放掉这块内存。

其中在开发时引用计数又分为ARC(自动内存管理)和MRC(手动内存管理)。ARC的本质其实就是MRC,只不过是系统帮助开发者管理已创建的对象或内存空间,自动在系统认为合适的时间和地点释放掉已经失去作用的内存空间,原理是一样的。虽然ARC操作起来很方便,不但减少了代码量,而且降低了内存出错的概率,但因为ARC不一定会及时释放,所以程序有时候可能会占用内存较大。而MRC若做得好,通过手动管理,及时释放掉不需要的内存空间,便可保证程序长时间运行在良好状态上。

在MRC中会引起引用计数变化的关键字有:alloc,retain,copy,release,autorelease。(strong关键字只用于ARC,作用等同于retain)

alloc:当一个类的对象创建,需要开辟内存空间的时候,会使用alloc,alloc是一个类方法,只能用类调用,它的作用是开辟一块新的内存空间,并使这块内存的引用计数从0增加到1,注意,是新的内存空间,每次用类alloc出来的都是一块新的内存空间,与上一次alloc出来的内存空间没有必然联系,而且上一次alloc出来的内存空间仍然存在,不会被释放。

retain:retain是一个实例方法,只能由对象调用,它的作用是使这个对象的内存空间的引用计数加1,并不会新开辟一块内存空间,通常于赋值是调用,如:

对象2=[对象1 retain];表示对象2同样拥有这块内存的所有权。若只是简单地赋值,如:对象2=对象1;那么当对象1的内存空间被释放的时候,对象2便会成为野指针,再对对象2进行操作便会造成内存错误。

copy:copy同样是一个实例方法,只能由对象调用,返回一个新的对象,它的作用是复制一个对象到一块新的内存空间上,旧内存空间的引用计数不会变化,新的内存空间的引用计数从0增加到1,也就是说,虽然内容一样,但实质上是两块内存,相当于克隆,一个变成两个。其中copy又分为浅拷贝、深拷贝和真正的深拷贝,浅拷贝只是拷贝地址与retain等同;深拷贝是拷贝内容,会新开辟新内存,与retain不一样;真正的深拷贝是对于容器类来说的,如数组类、字典类和集合类(包括可变和不可变),假设有一个数组类对象,普通的深拷贝会开辟一块新内存存放这个对象,但这个数组对象里面的各个元素的地址却没有改变也就是说数组元素只是进行了retain或者浅拷贝而已,并没有创建新的内存空间,而真正的深拷贝,不但数组对象本身进行了深拷贝,连数组元素都进行了深拷贝,即为各个数组元素开辟了新的内存空间。

release:release是一个实例方法,同样只能由对象调用,它的作用是使对象的内存空间的引用计数减1,若引用计数变为0则系统会立刻释放掉这块内存。如果引用计数为0的基础上再调用release,便会造成过度释放,使内存崩溃;

autorelease:autorelease是一个实例方法,同样只能由对象调用,它的作用于release类似,但不是立刻减1,相当于一个延迟的release,通常用于方法返回值的释放,如便利构造器。autorelease会在程序走出自动释放池时执行,通常系统会自动生成自动释放池(即使是MRC下),也可以自己设定自动释放池,如:

@autoreleasepool{ obj= [[NSObject alloc]init]; [obj autorelease]; }
当程序走出“}”时obj的引用计数就会减1.

除了以上所述的关键字,还有一些方法会引起引用计数的变化,如UI中父视图添加、移除子视图,导航控制器或视图控制器推出新的视图控制器以及返回,容器类(数组、字典和集合)添加和移除元素。

当子视图添加到父视图上时,子视图的引用计数加1,移除时引用计数减1,若父视图引用计数变为0内存被释放,其所有的子视图都会被release一次,即引用计数减1,原则上只有这三种情况子视图的引用计数会发生变化,其他如父视图引用计数的加减都不会影响到子视图。

容器类的情况与视图类似,添加元素,该元素引用计数加1,移除元素,该元素引用计数减1,容器引用计数变为0所占用内存被释放,容器所有元素release,引用计数减1,其他情况下容器本身的引用计数变化不会影响到容器内元素的引用计数变化。

导航控制器或视图控制器推出新的视图控制器会使被推出的视图控制器的引用计数加1,该视图控制器返回的时候引用计数减1,具体方法如下:

导航控制器推出视图控制器调用方法:- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;

返回时同样用导航控制器调用方法:- (UIViewController *)popViewControllerAnimated:(BOOL)animated;

视图控制器推出视图控制器调用方法:- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion

返回时被推出的视图控制器调用方法:- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion

应注意:当一个对象的引用计数变为0占用内存被释放时,会调用- (void)dealloc方法,所以如果在MRC下自定义类,必须在该方法里将该类中属性关键字设置为retain或copy的属性release一次,以免造成内存泄露,重写方法不要忘记在第一行添加[super dealloc];。

上面基本是我们了解的内存这一块。但是很有很多是我们日常接触不到的,在实际开发工程中我们用到,但是我们从来不关心的。下面我来说一下!

以前我有一个非常不好的开发习惯, 不知道为什么当时会那么做,估计是开发经验不足吧,我以前喜欢用传值的方式去进行数据的传递,后来一名老ios 告诉我这样写出来的程序性能非常差,除非到万不得已的情况下我们才去选择通知去传至!所以我放弃了传至。因为大部分的传至都可以使用属性进行传至, 这个我以前概念太浅薄了。

只要是内存中没有销毁的对象我们都可以对他进行重新赋值。我们不需要去重新去设置UI 只需要去设置数据源就可以了。

推荐阅读更多精彩内容