一、
内存管理
内存的作用: 存储数据.
1). 如何将数据存储到内存之中
声明1个变量,将这个数据存储进去
2). 当数据不再被使用的时候,占用的内存空间如何被释放-
内存中的五大区域
栈:局部变量,当局部变量的作用域被执行完毕之后,这个局部变量就会被系统立即回收.
堆:OC对象,使用C函数申请的空间
BSS段:未初始化的全局变量、静态变量,一旦初始化就回收,并转存到数据段中.
数据段:已经初始化的全局变量、静态变量,知道程序结束的时候才会被回收.
代码段:代码,程序结束的时候,系统会自动回收存储在代码段中的数据.栈、BSS段、数据段、代码段存储在它们中的数据的回收,是系统自动完成的.
-
分配在堆区的OC对象,是肯定需要被回收的.
iPhone 内存机制,如果内存占用内存达到40M会受到系统警告,120M会闪退.存储在堆中的OC对象,系统是不会自动回收的,知道程序结束的时候才会被回收。
内存管理的范围:
只需要管理堆中的OC对象的回收,其他区域中的数据是系统自动管理的.对象应该什么时候被回收?
当有人使用这个对象的时候,这个对象就不会被回收,只有在没有人使用的时候才可以被回收.引用计数器
1). 每一次对象都有一个属性,叫做retainCount(引用计数器),类型是unsigned long占据八个字节。
引用计数器的作用: 用来记录这个对象有多少人在使用它,默认情况下,创建一个对象出来,这个引用计数器的默认值是1.
2). 当多一个人使用的时候,这个引用计数器的值+1
3). 当少一个人使用的时候,这个引用计数器的值-1
4). 当这个对象的引用计数器变为0的时候,代表这个无人使用,系统就会自动回收它.如何操作引用计数器
1). 为对象发送1条retain消息,对象的引用计数器就会加1,当多一个人使用的对象的时候才发
2). 为对象发送1条release消息,对象的引用计数器就会减1,当少一个人使用的对象的时候才发
3). 为对象发送一条retainCount消息,就可以了取到对象的引用计数器的值
4). 当对象的引用计数器变为0的时候,对象就会被系统立即回收, 在对象被回收的时候,会自动调用对象的dealloc方法.-
内存管理的分类
1).MRC(Manual Reference Counting)手动引用计数,手动内存管理.
当多一个人使用对象的时候,要求程序员手动的发送retain消息;反之.
2). ARC(Automatic Reference Counting)自动引用计数,自动内存管理.
系统自动的在合适的地方发送retain realse消息.- 学习MRC理由:面试、早期的App开发使用的是MRC技术、ARC是基于MRC的.
二、MRC
Xcode默认的开发方式是ARC模式.
关闭ARC:Project->选中工程->Build Settings-> All->Apple LLVM7.0 - Language - Objective C ->Objective-C Automatic Reference Counting,设置为NO.-
当对象的引用计数器变为0的时候,系统会自动回收对象. 在系统回收对象的时候,会自动的调用对象的dealloc方法.
1). 重写类的dealloc方法, 必须要调用父类的dealloc方法,要放在最后一句代码的位置。- (void)dealloc{ NSLog(@"名字叫XXX消失了"); [super dealloc]; }
-
测试引用计数器
1). 新创建一个对象,这个对象的引用计数器的值默认是1.Person *p1 = [[Person alloc] init]; [p1 retainCount];// 获取引用计数器的值 [p1 retain];//为引用计数器加1 [p1 release];//为引用计数器减1
2). 当对象的引用计数器变为0的时候,对象就会被系统立即回收,并自动调用dealloc方法.
3). 为对象发送这个return消息,对象的引用计数器就会加1 为对象发送release消息并不是回收对象,而是让对象的引用计数器减1,当对象的引用计数器的值变为0的时候,对象才会被系统立即回收.
内存管理的重点
1). 什么时候为对象发送retain消息
当多一个人使用这个对象的时候,应该先为这个对象发送retain消息
2). 什么时候为对象发送release消息
当少一个人使用这个对象的时候,应该为这个对象发送一条release消息
在ARC机制下,retain、release、dealloc这些方法无法调用内存管理的原则
1). 有对象的创建,就要匹配一个release
2). retain和release的调用次数要匹配
3). 谁用谁retain,谁不用谁release
4). 只有在多一个人用的时候才retain,少一个人使用的时候才release,有始有终,有加就有减。野指针
C语言中的野指针: 定义一个指针变量,没有初始化,这个指针变量的值时一个垃圾值,指向一块随机的空间
OC中的野指针:指针指向的对象已经被回收了.内存回收的本质:
申请一个变量,实际上就是向系统申请指定字节数的空间,这段空间就不会分配给别人了,当变量被回收的时候,代表变量占用的字节空间可以分配给别人了,但是这个字节空间中存储的数据还在。
对象回收的本质:指的是对象占用的空间可以分配给别人了,当这个对象占用的空间没有分配给别人之前,其实对象数据还在.僵尸对象
1个已经被释放的对象,但是这个对象所占空间还没有分配给别人。通过野指针访问僵尸对象的时候,有可能没问题,也有可能有问题。当僵尸对象占用的空间还没有分配给别人使用的时候,还是可以的;反之。-
我们认为只要对象成为了僵尸对象,无论如何都应该不允许访问了。如果访问的是僵尸对象,无论如何都报错。
僵尸对象的实时检查机制,可以将这个机制打开,打开之后,只要访问的是僵尸对象,无论空间是否分配,就会报错。停止运行按钮右侧的Target切换点击-> Edit Scheme...->Run->Diagnostics->勾选Enable Zomble Objects.
为什么不默认打开僵尸对象检测
一旦打开僵尸对象检测,那么在每访问一个僵尸对象的时候,都会先检测这个对象是否是一个僵尸对象,这样是极其消耗性能的.如何避免僵尸对象错误?
当一个指针成为野指针以后,将这个指针的值设置为nil.
当一个指针为nil时,通过这个指针调用对象的方法的时候,不会报错,只是没有任何反应,但是如果通过指针直接访问属性就会报错.无法复活一个僵尸对象
三、
内存泄漏
指的是一个对象没有被及时的回收,在该回收的时候没有及时回收,一直驻留在内存中,知道程序结束的时候才结束.单个对象的内存泄漏情况
1). 有对象的创建,而没有对应的release
2). retain的次数和release的次数不匹配
3). 在不适当的时候为指针赋值为nil
4). 在方法中为传入的参数不适当的retain如何保证单个对象可以被回收
1). 有对象的创建,就必须要匹配一个release
2). retain和release次数一定要匹配
3). 只有在指针成为野指针时才赋值为nil
4). 在方法中不要随意的为传入的对象retain-
多个对象的内存管理
1). 当属性是一个OC对象的时候,setter方法的写法.
将传进来的对象赋值给当前对象的属性,代表传入的对象多一个人使用,所以我们应该先为这个传入的对象发送一条retain消息再赋值.当当前对象销毁的时候,代表属性指向的对象少一个人使用,就应该在dealloc中release.
代码写法:- (void)setCar:(Car *)car{ _car = [car retain]; } - (void)dealloc{ [_car release]; [super dealloc]; }
2). 当属性是一个OC对象的时候,setter方法按照上面写还是有bug.
当为对象的这个属性多次赋值的时候,就会发生内存泄漏,发生泄漏的原因: 当为属性赋值的时候,代表就对象少一个人用,新对象多一个人用,应该release,旧对象retain新的.
代码写法:- (void)setCar:(Car *)car{ [_car release]; _car = [car retain]; } - (void)dealloc{ [_car release]; [super dealloc]; }
3). 出现僵尸对象的原因,新旧对象是同一个,当发现新旧对象是同一个的时候,什么都不用做.只有当新旧对象不是同一对象的时候,才release旧的.
最终代码写法:- (void)setCar:(Car *)car{ if(_car != car){ [_car release]; _car = [car retain]; } } - (void)dealloc{ [_car release]; [super dealloc]; }
4). 特别注意: 内存管理的范围是OC对象,这个属性的setter方法才要像上面那样写.
-
@property参数
1). 作用
a. 自动生成私有属性
b. 自动生成这个属性的getter/setter的声明
c. 自动生成这个属性的getter/setter的实现
特别播报: 生成的setter方法实现中,无论什么类型,都是直接赋值.
2). @property参数
a. @property可以带参数
@property(参数1, 参数2, 参数3...)数据类型 名称;
b. @property的四组参数
a). 与多线程相关的两个参数
atomic/nonatomic
b). 与生成的setter方法的实现相关的参数
assign/retain
c). 与生成只读、读写相关的参数
readonly/readwrite
d). 与生成getter/setter方法名相关的参数
getter/setter3). 与多线程相关的参数(atomic/nonatomic)
atomic: 默认值,这个时候生成的setter方法的代码就会被加上一把线程安全锁.特点: 安全、效率低
nonatomic: 生成的setter方法的代码不会加线程安全锁,但是效率高.
建议要效率,选择使用nonatomic
4). 与生成的setter方法的实现相关的参数(assign/retain)
assign: 默认值,生成的setter方法实现就是直接赋值
retain: 生成的setter方法的方法实现就是标准的MRC内存管理代码,先判断新旧对象是否为同一对象,如果不是就release旧的,retain新的.
当属性的类型是OC对象类型的时候就使用retain,当属性的类型实时非OC对象的时候就使用assign.
注意: retain只是生成标准的setter方法为标准的MRC内存管理代码,不会自动的在dealloc中生成release的代码,需要手动添加.
5). 与生成只读、读写相关的参数readonly/readwrite)
readwrite: 默认值,代表同时生成getter/setter
readonly: 只会生成getter,不会生成setter6). 与生成getter/setter方法名相关的参数(getter/setter)
默认情况下: @property生成的getter/setter是标准类型的,其实我们可以通过参数来指定@property生成的方法的名字
setter: setter=setter方法名字 用来指定@property生成的setter的方法的名字,注意,setter方法是带参数的,要加一个冒号
getter: getter=getter方法名字 用来指定@property生成的getter方法的名字
注意: 如果使用setter/getter修改了生成方法的名字,在使用点语法的时候,编译器在转换的时候转换成修改后的名字的代码.
使用建议:
a. 无论什么情况都不要改setter方法的名字,默认情况下生成的名字已经是最标准的了
b. 什么修改getter方法的名字,当属性的类型是一个BOOL类型的时候,就修改这个getter的名字以is开头,提高代码的阅读性. @class
1). 当两个类相互引用的时候,当Person.h中包含Book.h,而Book.h中又包含Person.h,这个时候就会造成无限递归,而导致无法编译通过.
2). 解决方案:
其中一个类中不要使用#import引入对方的头文件,而是使用@class 类名,来标注这是一个类,这样就可以在不引入对方头文件的情况下,告诉编译器这是一个类。
在.m文件当中再#import对方的头文件
3). @class与#import的区别
a. #import是将指定的文件的内容拷贝到写指令的地方
b. @class并不会拷贝任何内容,只是告诉编译器这是一个类,这样编译器在编译的时候才可以指定这是一个类。循环retain
1). 当两个对象相互引用的时候
A对象的属性是B对象,B对象的属性是A对象.如果两边都使用retain,就会发生内存泄漏
2). 解决方案
一段使用retain,一段使用assign,使用assign的那一段在dealloc中不再需要release.