1.内存管理的范围
程序在系统中分配内存有五个地方:BSS段 数据区 代码区 (这三个是在程序运行时加载到内存空间的)
栈区(存储的是局部变量等系统自动回收的数据,高地址->低地址) 堆区(存储动态分配的数据,低地址->高地址)
所以在OC中的内存管理,就是管理堆区中的对象。也就是继承自NSObject的对象。
2.原理以及分类
对象的所有权以及引用计数:
一个对象可能有不止一个所有者。只要还有一个所有者,就继续存在。
retain方法可以获得对象的所有权,每个对象在创建之初就有一个retainCount引用计数器,它是unsigned long类型,在内存中占8个字节。
引用计数器是判断一个对象是否被回收的重要依据,当使用new,copy或者alloc创建一个对象时,默认为1。当retainCount为0时,回收对象。但是有一种特殊情况,当对象被赋值为nil类型时,不回收。为啥?nil是空值,没分配内存,回收个毛线。
retain消息 :加1操作,返回对象的本身;
release消息: 减1操作;
retainCount消息:计数器的值,用%ld,%tu,%lu 表示
dealloc消息:当一个对象被回收时,发送dealloc消息。
一般重写dealloc方法,会用到[super dealloc]只能放在最后调用。
内存管理只要有三类:MRC(手动内存管理)ARC(自动内存管理,底层就是MRC)垃圾回收机制(苹果不支持)
主要使用ARC,方便╮(╯_╰)╭,但是要理解MRC,本质( ⊙ o ⊙ )
3.MRC(手动内存管理)简介
Xcode默认是ARC机制的,需要手动更改,在Build Setting中,搜索auto,找到ARC。
Person *p = [Person new]; //retainCount ==1
[p retain]; // 2
[p release]; //1
[p release]; // 0
-(void) dealloc{
NSLog(@"已进入下一次轮回序列... ... ");
[super dealloc];// 子类释放后,父类也得来一下,不然显得不那么情深意浓
}
管理原则:找到平衡很重要,用就加1,不用就减1.有人使用就不回收。
谁创建 谁release;谁retain,谁release;
管理内容:
野指针:定义的指针变量没有初始化;指向的空间已经被释放;
内存泄露:内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。即所谓内存泄漏。
单个对象的内存管理:
是否检测僵尸对象,需要手动更改。如果不检测,野指针当中有无意义的数据。不能用retain使僵尸对象复生。
给空指针发送消息是没有反应的。避免使用僵尸对象的方法是:给僵尸对象赋值为nil。
多个对象的内存管理:
一个对象在销毁之前可以任意调用方法,但是这个对象可能关联的其他对象已经被销毁。注意释放顺序。
set方法的内存管理:
如果对象作为另一个类的实例变量。先判断传递对象是否为新对象,如果是,对旧对象release,对新对象retain
if(_car != car) {
[_car release];
_car = [car retain];}
如果不是 ,什么也不做就可以。
@property参数 (1,2)
原子性: atomic 对属性加锁,多线程下线程安全,默认值。
nonatomic 对属性不加锁,不安全但是速度快
读写属性:readwrite 生成getter setter方法,默认值
readonly 只生成getter方法
set方法处理:assign 直接赋值,默认值 //实例变量是基本数据类型
retain 先release原值,再retain新值
copy 先release原值,再copy新值
如@property (nonatomic,retain)Car *car;
注意重写dealloc方法;
替换set和get方法名称:
@property (nonatomic,assign )BOOL vip;
@property(nonatomic,assign setter = isVip:,getter= isVIP)BOOL vip;//注意set方法的冒号:
@class的使用:@class 类名xx;
#import 是拷贝内容,当发生变化的时候需要重新拷贝。
@class 告诉编译器是xx一个类,有哪些属性方法,不去检测,好像在说,哥们相信我!
但是编译器不是个傻子,它只是相信是个类,至于里面的属性方法,用的时候会出问题,所以需要#import xx.h
当涉及到循环引用的时候,#import 是拷贝内容就会出错,而@class不会。
循环retain的问题:
引用头文件时需要使用@class
循环retain会导致两个对象都存在内存泄露问题。
推荐方法:
1)让某个对象多释放一次,但是需要注意顺序;
2)一端使用retain,一端使用assign;
NSString类的内存管理:
字符串的常量 会存在字符串的常量池当中,如果使用的字符串已经存在,不会重新分配内存。
常量区的引用计数是无符号的最大值,如果release 没有结果
initWithFormat 、stringWithFormat 分配在堆区 //同样不要release 因为没alloc
stringWithString、initWithString、@“ad”、alloc init分配都是在常量区
轻易不要使用retainCount作为判断条件,有可能循环好久好久... ...
autorelease的使用:
自动释放池,@autoreleasepool {}(5.0以后),它是一个特殊的栈结构(先进后出),结束时会向池中的对象自动发送release消息。
- 创建自动释放池
- 对象加入自动释放池 [对象名 autorelease];加入不会改变计数器的值
autorelease的应用场景:
创建一个类方法,使其自动创建对象并且添加到自动释放池。
+(id) person{
return [[Person alloc] init autorelease]; //返回对象的空间
}
系统自带很多类具有这种方法:
NSArrary *arr = 【NSArray array】;
NSDictionary *dic = 【NSDictionary dictionary】;
动态类型:
Student *stu = 【Student person】;//子类使用父类的方法
+(id) person{
return [[self alloc] init autorelease]; //返回对象的空间
}
存在的问题:
当赋值两遍的类型不一致的时候,使用返回值id在编译的时候是不会报错的。
id和instancetype的区别:
instancetype可以判断返回值类型和赋值的指针类型(接收类型)是否一致。id不能智能检测。
当一个方法的返回值是对象的时候,最好使用instancetype类型。
+(instancetype)person{
return [[Person alloc] init autorelease];
}