OC第七节——内存管理
一、内存分配
1、栈区(stack):由编译器自动分配释放,存放函数的参数,局部变量的值等;栈是向低地址扩展的数据结构,是一块连续的内存区域;一些值类型(int、float、struct等基本类型)就存放在栈中;
2、堆区(heap):一般由程序员分配管理,是由alloc分配的内存,一般速度比较慢,容易产生内存碎片;堆是向高地址扩展的数据结构,是不连续的内存区域;一些引用类型(继承自NSObject类的所有的OC对象)存在于堆中;
3、全局区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域;
4、常量区:常量字符串就是放在这里的
5、代码区:存放函数体的二进制代码
二、内存管理方式
1、(NSAutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机。
2、ARC (Automatic Reference Counting),自动引用计数,由xcode,帮我们去管理内存。
3、MRC (Manual Reference Counting),手动引用计数,我们手动管理内存。
三、内存管理原理
引用计数:对于一块动态申请的内存,有一个人(指针)使用,就给这个内存的计数器加1,使用完成后,就给这个计数器减1,当这个内存的引用计数为0了,我们就销毁它,这就是引用计数,OC,就是使用引用计数这种方式来管理内存的。
四、所有权修饰符
ARC有效时,id类型和任何对象类型(对象类型就是指向NSObject这样的Objective-C类的指针)都必须附加所有权修饰符,所有权修饰符一共有四种:
- __strong修饰符;
- __weak修饰符;
- __unsafe_unretained修饰符;
- __autoreleasing修饰符;
__strong修饰符:
默认情况下,使用的是__strong修饰符。
它表示的是对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
__weak修饰符:
只使用_strong修饰符,有可能产生“循环引用”的问题。__weak修饰符与__strong修饰符相反,提供弱引用。弱引用不能持有对象实例。
__unsafe_unretained修饰符
__unsafe_unretained修饰符与__weak修饰符一样不持有对象实例。但是赋值给附有__unsafe_unretained修饰符变量时,如果其值不存在时,程序会立即崩溃。
__autoreleasing修饰符
__autoreleasing修饰符等价于在ARC无效时调用对象的autorelease方法。
属性声明与所有权修饰符的对应关系
属性声明的属性 | 所有权修饰符 |
---|---|
assign | __unsafe_unretained修饰符 |
copy | __strong修饰符(但是赋值的是被复制的对象) |
retain | __strong修饰符 |
strong | __strong修饰符 |
unsafe_unretained | __unsafe_unretained修饰符 |
weak | __weak修饰符 |
补充:
1.atomic,线程安全,一般情况下如果在多线程开发中一个属性可能会被两个及两个以上的线程同时访问使用(读写安全,不能release)
2.nonatomic,非原子操作,访问器简单返回这个值,没有加锁。
3.readwrite,可读可写,生成set和get方法
4.readonly,只读特性,生成get方法,不生成set方法
weak与strong的区别
weak弱引用,单纯的指向某个地址,本身并未分配内存地址,当指向的地址被销毁时,指针会自动被置nil;
strong是强引用,类似于retain,会将对象的引用计数+1,分配内存地址;
weak与assign的区别
1、修饰变量类型的区别
- weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错。
- assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是"unsafe_”。
2、是否产生野指针的区别
- weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
- assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
strong与copy的区别
- strong对应的setter方法,是将_property先release([_property release]),然后将参数retain([property retain]),最后_property = property.
- copy对应的setter方法,是将_property先release([_property release]),然后将参数内容copy([property copy]),创建一块新的内存地址,最后_property = property.
weak的实现
weak的实现是基于哈希表,对象中的属性被weak修饰时,会以对象的地址为key,属性的地址为value,存储到哈希表中。当对象被销毁时,运行时会通过哈希表找到所有用weak修饰的属性,将其指针自动置为nil。
浅拷贝与深拷贝
浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针。
深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。
copy方法:如果是非可扩展类对象(比如NSString,NSArray),则是浅拷贝。如果是可扩展类对象(比如NSMutableString,NSMutableArray),则是深拷贝。
mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。
五、MRC使用
1.Xcode 5.0 以后的版本默认是ARC模式,工程创建的时候是ARC的,我们如果想要MRC,需要进行如下设置。
选中工程 - target - Bulid Settings - 搜索:automatic reference counting或auto,将Objective-C Automatic Reference Counting改为NO。
2.MRC管理内存原则:如果对一个对象使用了alloc、[mutable]copy、retain,那么你必须使用相应的release或者autorelease,简而言之就是:谁创建,谁负责释放;
注意:
1.向集合(NSArray,NSDictionary等)添加对象时,被添加的对象会被执行retain操作,当从集合中移走对象或者集合对象被释放时,集合中的对象会被执行release操作。
2.在程序中直接用@""创建的NSString对象,是常量,引用计数是-1,向它发送retain、release没有效果。
3.retain,retainCount与release
retain,将对象进项保留操作,也就是使对象的引用计数加1。
retainCount,打印一个对象的引用计数。
release,引用计数减1
DSDog*dog=[[DSDog alloc]init]; NSLog(@"%lu",[dog retainCount]);//1 含有init,引用计数加1 DSDog *dog2=[dog retain]; NSLog(@"%lu",[dog2 retainCount]);//2 对象retain,引用计数加1 [dog2 release]; NSLog(@"%lu",[dog2 retainCount]);//1 release,引用计数减1 [dog release]; // 变为0 ,销毁对象
4.@property retain,assign,copy展开
4.1@property (nonatomic, retain) DSDog *dog;
retain用来修饰ObjC对象。
则会展开如下:
- (void)setDog:(DSDog *)dog
{
if (_dog != dog)
{
[_dog release];
_dog = [dog retain];
}
}
4.2 assign展开:简单数据类型
@property (nonatomic, assign) int number;
assign是直接赋值,用来修饰简单数据类型 ,OC的内存管理对于简单的数据类型 int\float…, OC无效
则会展开如下:
- (void)setNumber:(NSInteger)number
{
_number = number;
}
4.3 copy展开 , 复制一份原来的对象
//copy 多用于字符串,Dict、Array
@property (nonatomic, copy)NSString *name;
- (void)setName:(NSString *)name
{
if (_name != name)
{
[_name release];
_name = [name copy];
}
}
六.自动释放池
原理:对象接收到autorelease消息时,它会被添加到了当前的自动释放池中,当自动释放池被销毁时,会給池里所有的对象发送release消息。
ObjC提供两种方法创建自动释放池:
方法一:使用NSAutoreleasePool来创建
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init]
//这里写代码
[pool release];
方法二:使用@autoreleasepool创建
@autoreleasepool {
//这里写代码
}
七.ARC
下哪些情况会造成内存泄漏?
1.
block中的循环引用(block里面用weakSelf去调用)
2.NSTimer的循环引用(在控制器中使用NSTimer会使当前控制器引用计数加1,所以在控制器释放之前,必须暂停和使定时器失效,否则控制器将不会被释放。)
3.addObserver的循环引用
4.delegate的强引用(用weak修饰Delegate)
5.大次数循环内存爆涨(放入内存池)
6.非OC对象的内存处理(需手动释放)