平时的积累记录下,记性差,方便平时查找
一 ARC和MRC的 小入门
1 MRC在ARC之前,MRC就是传统的手工内存管理方法,内存的分配和回收任务全部都落在程序员身上了。ARC是自动内存管理方法。
在iOS 5 和Xcode4.2之前,ARC还没出现,那时候使用OC的程序员在编写代码时还必须考虑内存管理的问题,并在代码中假如retain,release,dealloc等关键词来管理内存,而且必须小心谨慎,不然就会出现内存泄露和无效指针,甚至导致程序崩溃,每一个alloc,retain,copy都会有release与之对应,工作量可想而知
内存管理:顾名思义就是管理自己创建的涉及内存的指针,对象。当你新建一个对象,完成了对它的使用,你就要 自行把它从内存中释放出来,不然就造成了内存泄露,当你新建一个指针,所记录的地址的对象已经被释放,那么这就是一个无效指针,同样增加了程序的不稳定性。
在MRC中,存在着如此的对象管理规则,每一个对象被创建以后,都会有一个Reference Count,初始化为1.当你使用完时就要release掉它,使他的Reference Count 减1,当一个对象的Reference Count 为0 时,那么系统就会把它的内存释放掉
retain使Reference Count 加1 , release 使Reference Count 减1。
混合开发
ARC模式下用MRC文件
方法一:使用Xcode自带的转换功能,将MRC文件转化为ARC(容易出错)Edit—>covert —>To Object-C ARC
方法二:设置标识,告诉编译器,哪个类该用MRC模式
Build phases 选项下Compile Sources 下选择不使用arc编译的文件。双击它,输入-fno-objc-arc即可
方法三:当MRC的类比较多时,就打包成静态库(http://blog.csdn.net/qq_30970529/article/details/51076216?locationNum=14)
MRC模式下用ARC文件
build phases 选项下Compile Sources 下选择要使用arc编译的文件。双击它,输入-fobjc-arc 即可
虚拟内存和物理内存:
我们的程序访问的都是逻辑地址空间(也叫虚拟地址),逻辑地址需要经过转换之后才可以访问到物理内存,
主要是两个寄存器在中间起到了强大的作用,界限寄存器用来判断是否越界,如果没有越界就会加上基址寄存器的值,转换为物理内存地址。
优先级由低到高是:IDLE(空闲)->BACKGROUND->FOREGROUND,依次类推。当内存过低的时候,就会在队列中进行广播,希望大家尽量释放内存,如果一段时间后,仍然内存不够,就会开始Kill进程,直到内存够用。
1 引用计数器:当对象没有被任何对象引用(没有指针指向该对象)时,就会被释放
使用自动释放池需要注意:
1)自动释放池实质上只是在释放的时候給池中所有对象对象发送release消息,不保证对象一定会销毁,如果自动释放池向对象发送release消息后对象的引用计数仍大于1,对象就无法销毁。
2)自动释放池中的对象会集中同一时间释放,如果操作需要生成的对象较多占用内存空间大,可以使用多个释放池来进行优化。比如在一个循环中需要创建大量的临时变量,可以创建内部的池子来降低内存占用峰值。
3)autorelease不会改变对象的引用计数
二 修饰符的介绍
基本数据类型变量:被基本数据类型修饰 实例变量:变量的类型是一个类,UIButton *, NSString * 等 ,成员变量包含:基本数据类型变量和实例变量
属性:被property声明,自动生产set和get方法
1 property 声明时有两个选择
1 @synthesize 告诉编译器在编译期间生成set,get方法
2 @dynamic 自己实现set,get方法
2 我们在.m里面声明的变量子类是无法访问的(即使给他@public),也会被认为是@private,所以我们的对外属性都会放到.h去声明,然而由于six变量是@private,所以子类还是无法访问的
3 {
.h文件大括号中的是成员变量,没有setget方法,不能被外部类访问,
}
4 类内使用成员变量{}, 类外使用属性@property.
因此在类外的话, 强烈推荐使用属性@property.
而如果非要在类外使用成员变量{}, 则要么将该成员变量设为@public, 要么自定义其get/set方法, 利用这两个方法从类内部对成员变量进行调用或赋值.
1)当你通过new、alloc或copy方法创建一个对象时,它的引用计数为1,当不再使用该对象时,应该向对象发送release或者autorelease消息释放对象。2)当你通过其他方法获得一个对象时,如果对象引用计数为1且被设置为autorelease,则不需要执行任何释放对象的操作;3)如果你打算取得对象所有权,就需要保留对象并在操作完成之后释放,且必须保证retain和release的次数对等。
__strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值,所有的实例变量和局部变量都是strong类型,只要该对象被strong指针指向,该对象就不会被销毁,如需强制释放,可置nil;__weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。__autoreleasing:自动释放对象的引用,一般用于传递参数
属性的参数分为三类,基本数据类型默认为(atomic,readwrite,assign),对象类型默认为(atomic,readwrite,strong),其中第三个参数就是该属性的内存管理方式修饰,修饰词可以是以下之一 当然也可以修饰ObjC对象,但是不推荐,因为被assign修饰的对象释放后,指针还是指向释放前的内存,在后续操作中可能会导致内存问题引发崩溃。
使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放的的问题。
5copy,strong,weak,assign的区别。
可变变量中,copy是重新开辟一个内存,strong,weak,assgin后三者不开辟内存,只是指针指向原来保存值的内存的位置,storng指向后会对该内存引用计数+1,而weak,assgin不会。weak,assgin会在引用保存值的内存引用计数为0的时候值为空,并且weak会将内存值设为nil,assign不会,assign在内存没有被重写前依旧可以输出,但一旦被重写将出现奔溃
不可变变量中,因为值本身不可被改变,copy没必要开辟出一块内存存放和原来内存一模一样的值,所以内存管理系统默认都是浅拷贝。其他和可变变量一样,如weak修饰的变量同样会在内存引用计数为0时变为nil。
容器本身遵守上面准则,但容器内部的每个值都是浅拷贝。
**综上所述,当创建property构造器创建变量value1的时候,使用copy,strong,weak,assign根据具体使用情况来决定。value1 = value2,如果你希望value1和value2的修改不会互相影响的就用用copy,反之用strong,weak,assign。如果你还希望原来值C(C是什么见示意图1)为nil的时候,你的变量不为nil就用strong,反之用weak和assign。weak和assign保证了不强引用某一块内存,如delegate我们就用weak表示,就是为了防止循环引用的产生。
另外,我们上面讨论的是类变量,直接创建局部变量默认是Strong修饰
6 delegate为什么要用weak或者assign而不用strong
a创建对象b,b中有C类对象c,所以a对b有一个引用,b对c有一个引用,a.b引用计数分别为1,1。当c.delegate = b的时候,实则是对b有了一个引用,如果此时c的delegate用strong修饰则会对b的值内存引用计数+1,b引用计数为2。当a的生命周期结束,随之释放对b的引用,b的引用计数变为1,导致b不能释放,b不能释放又导致b对c的引用不能释放,c引用计数还是为1,这样就造成了b和c一直留在了内存中。
而要解决这个问题就是使用weak或者assign修饰delegate,这样虽然会有c仍然会对b有一个引用,但是引用是弱引用,当a生命周期结束的时候,b的引用计数变为0,b释放后随之c的引用消失,c引用计数变为0,释放。
在MRC中使用assign修饰代理,记得在dealloc中把代理置nil,不然会和assgin一样,造成野指针
ARC中使用weak
7 深拷贝和浅拷贝
浅拷贝是创建一个新的指针指向原来的对象,引用计数器加一。深拷贝是创建一个新的指针和新的对象,让新的指针指向新的对象,新的对象引用计数器为1,老的引用计数器为1.
copy是创建一个新的对象,retain是创建一个新的指针,原有对象引用计数器加一,copy属性表示两个对象内容相同,新的对象retain为1,与旧有对象的引用计数器无关,就有对象没有变化,copy减少了对象对上下文的依赖。
retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1也即是说,retain是指针拷贝,copy是内容拷贝
[array addObject:obj];
这样obj的引用计数会增加1,如果使用remove则obj的引用计数会减一。
ios对集合的内存处理就是这样的。
那么,假设obj只被array拥有:
id temp = [array objectAtIndex:0];
[array removeObjectAtIndex:0];
如果你再要使用temp就会出错,因为这个时候obj已经被释放了。
NSString *str1 = @"str1"; // copy 原则: 修改源对象的属性和行为不会对副本对象造成影响,修改副本对象对源对象也没影响,所以后面修改了str1 ,str2 不变(对于mutableCopy同样适用)
NSString *str2 = [str1 copy]; // 使用copy str1 和str2 的值和地址都是一样的
NSString *mutableCopyStr = [str1 mutableCopy]; // str1 和 str2 的值 是一样,但是地址不一样
str1 = @"haha";// 修改了str1后,str2的值不变,但是地址变了,因为:str1 = str2,都是不可变参数,值一样系统就没必要开辟新的空间, 值改变后 就会开辟新空间了
数组:
用copy修饰和赋值的变量 肯定是不可变的,(可变数组和可变字符串 用 copy 修饰和赋值 会变成不可变的)
NSArray *arr = @[@"123", @"456", @"789"];
NSMutableArray *arr1 = [arr mutableCopy]; 用mutableCopy把不可变数组给可变数组赋值
NSMutableArray *marr2 = [arr copy]; marr2 和 arr 的地址一样, 因为都为不可变数组,内容一样,没必要开辟新空间 ,arr1 和arr不一样,arr1位可变数组,重新开辟了空间
NSMutableArray *arr1 = [NSMutableArray arrayWithCapacity:1];
NSMutableArray *marr2 = [arr1 copy];
[marr2 addObject:@"hh"]; // 报错
NSMutableDictionary *dic1 = [NSMutableDictionary new];
NSMutableDictionary *dic2 = [dic1 copy];
NSLog(@"dic1:%p ------\n dic2: %p",dic1,dic2);
NSString *TYPE1 = [dic2 fileType]; //报错,为不可变字典
NSMutableString *tempMStr = [[NSMutableString alloc] initWithString:@"strValue"];
NSMutableString *tempmmmstr = nil;
tempmmmstr = [tempMStr copy];
[tempMStr appendString:@"hhh"]; // 报错,不可变字符串不能从内部修改值,只能通过外部,或者直接赋值
可变字符串,可变数组,可变字典被相应的可变类型copy 赋值后变为 不可变数组或者不可变字典(copy 赋值的 都为不可以变的)
注意的深拷贝也仅仅是新建一个Mutable对象,而原对象如果保存有其他对 象(比如数组),那么里面的对象则是 retain 操作,Apple文档将这个称为集合的单层深拷贝。
浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制,即retain操作。
深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制,即单层深拷贝。(比如Array只深复制Array的内存地址,里面数组元素浅拷贝)
完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。
集合的完全复制有两种方法:
1 调用对象本身写好的API(如initWithArray: copyItems: 和 initWithDictionary: copyItems:,最后的参数copyItems设置为YES)
执行个这种方法,集合里的每个对象都会收到 copyWithZone: 消息。这个方法要求集合里的所有对象都实现 NSCopying 协议(copy操作),如果对象没有实现 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。2
将集合进行归档(archive),然后解档(unarchive),就可以实现完全深复制,mutable对象会进行mutableCopy,而immutable对象会新建一块内存复制内容。(同样需要所有对象都实现 NSCopying、NSMutableCopying 协议)
demo地址:内存管理小demo