项目中关于内存管理的问题
Posted kaihaOS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了项目中关于内存管理的问题相关的知识,希望对你有一定的参考价值。
OC 中内存管理机制。与 retain 配对使用的方法是 dealloc 还是 release ,为什么?需要与 alloc 配对使用的方法是 dealloc 还是 release ,为什么? readwrite , readonly,assign,retain,copy,nonatomic,atomic,strong,weak 属性的作用? OC 中内存管理机制应该就是引用计数机制, retainCount 为 0 时释放该内存。 retain 对应的是 release ,内存的释放用 release 。 alloc 对应的是 dealloc ,内存的销毁用 dealloc 。 readwrite 此标记说明属性会被当成读写的,这也是默认属性。 readonly 此标记说明属性只可以读,也就是不能设置,可以获取。 assign 不会使引用计数加 1 ,也就是直接赋值。 retain 会使引用计数加 1 。 copy 建立一个索引计数为 1 的对象,在赋值时使用传入值的一份拷贝。 nonatomic :非原子性访问,多线程并发访问会提高性能。 atomic: 原子性访问。 strong: 打开 ARC 时才会使用,相当于 retain 。 weak: 打开 ARC 时才会使用,相当于 assign ,可以把对应的指针变量置为 nil 。 什么叫做循环引用 对象A创建并引用到了对象B,对象B创建并引用到了对象C,对象C创建并引用到了对象B,这个时候B的引用计数是2,而C的引用计数是1,当A不用B的时候,就释放了B的所有权,这个时候C还引用对象B,所有B不会释放,引用计数为1;因为B也引用着对象C,B不释放,那么C就不会被释放,所以它们的引用计数都为1,并且永远不会被释放,所以形成了循环引用。 1.分别写出MRC中在assign、retain、copy下属性name对应的setter方法的内部实现。 assign下 - (void) setName:(NSString*)name _name = name; retain下 - (void) setName:(NSString*)name if(_name != name) [_name release]; _name = [name retain]; copy下 - (void) setName:(NSString*)name if(_name != name) [_name release]; _name = [name copy]; 2.ARC 下 dealloc 方法存在的意义在于什么地方?举例说明一下具体的使用场景。 答:其实在 MRC 中 dealloc 方法存在的主要意义是为了释放自身的实例变量,移除观察者,停止 timer ,移除通知,代理置空等。 ARC 下,系统会帮助我们释放该对象所包含的实例变量,但是有些对象还是需要们自己去释放的(比如 Core Foundation 框架下的一些对象),另外通知中观察者的移除,代理置空,停止 timer 等 示例如下所示: - (void)dealloc [[NSNotificationCenter defaultCenter] removeObserver:self];// 移除通知观察者 [[XMPPManager sharedManager] removeFromDelegateQueue:self];// 移除委托引用 [[MyClass shareInstance] doSomething ]// 其他操作 scrollView.delegate = nil; [timer invalidate]; 3.@synthesize 和 @dynamic 有什么区别? (1)@property 有两个对应的词,一个是 @synthesize ,一个是 @dynamic 。如果 @synthesize 和 @dynamic 都没写,那么默认的就是 @syntheszie var = _var; (2)@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。 (3)@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var ,然后你没有提供 @setter 方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar ,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = instance.var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。 @dynamic 可用于在分类中添加属性(需要用到 objc_getAssociatedObject 和 objc_setAssociatedObject 函数)。 4. 你如何看待 ios 中的拷贝? 答:在我看来,日常生活中,当我们用到 ” 拷贝 ” 这个词语的时候,不管怎样都会产生两份。一份是原来的,一份是新拷贝出来的。但是到目前为止,在 iOS 中我看到了三种拷贝方式: (1) 伪拷贝:伪拷贝,顾名思义,就是假拷贝,没有拷贝出新的对象。这一点对于 NSString 这种类簇来说比较常见, NSString 本身是不可变字符串,内容不可能被修改,因此我们也没有拷贝的必要,因为拷贝的目的是防止原件被修改,所以才拷贝出来一份,伪拷贝实际上是对象引用计数加了 1 (相当于 retain 或者 strong 的功效)。 (2) 浅拷贝:浅拷贝就是确实拷贝出来一份和原来内容一样的新对象。但是对于对象自带的属性是伪拷贝,两个对象的属性指向同一个内存。 (3) 深拷贝:深拷贝就是不仅仅拷贝出一份新对象,而且对象的属性也拷贝了一份。 总的来说,如果在开发的过程中需要实现拷贝,那么需要接受 NSCopying 协议。实现 copyWithZone: 方法。浅拷贝、深拷贝的区别在于 copyWithZone: 方法的实现不同。 16. 定义属性时,什么情况使用 copy , assign ,和 retain ? 答: assign 用于简单数据类型,如 NSInteger,double,bool 。 retain 和 copy 用于对象类型,二者又有区别, copy 是用于一个对象有可能被修改,但不想修改原件,所以拷贝一份出来(新拷贝的对象引用计数为 1 ),这样新拷贝的修改了,原件不会变,原件修改了,新拷贝的不会变。而 retain 呢,对象引用计数 +1 ,对象只有一个,但引用计数为 2 ,通过任何一个引用修改对象内容,另外一个引用使用的时候,用的就是修改之后的内容。 1 、以下代码有什么问题吗?如果没有问题的话, obj 、 obj2 的引用计数分别是多少?如果有问题的话存在什么问题? Class *obj = [[Class alloc]init]; Class *obj2 = obj; [obj hello]; [obj release]; [obj2 hello]; [obj2 release]; 答:上面的代码是存在问题的。当执行完第一行代码时,因为执行了 alloc , obj 的引用计数为 1 。第二行代码执行完之后, obj2 只是和 obj 指向了同一块内存。第三行代码是执行了 hello 方法。第四行代码执行 release 消息之后, obj 的引用计数减一,这时 retainCount 变为 0. 系统自动调用 dealloc 方法,对象被销毁。第五行代码执行时, obj2 执行的内存已经被系统回收了,但还是调用了 hello 方法,出现了问题(野指针)。第六行执行时, obj2 所指向的内存已经不存在,再次调用 release 消息,出现过度释放的问题,而且 obj2 已经变成野指针了。 Class *obj = [[Class alloc]init];//obj 引用计数加 1 Class *obj2 = obj;//obj , obj2 指向同一块内存(对象) [obj hello]; [obj release];//obj 指向的内存(对象)被销毁 [obj2 hello];// 错误 ,obj2 指向的内存(对象)已经被销毁了 [obj2 release];//[obj release] 之后 ,obj2 是个野指针 , 不应该再去调用方法 . 2 、在实际开发的过程中,什么情况下需要创建自动释放池?下面代码中有没有什么问题? Person *p1=[[Person alloc]init]; @autoreleasepool [p1 autorelease]; @autoreleasepool [p1 autorelease]; 答:其实自动释放池存在的意义是为了延迟释放一些对象,延迟向对象发送 release 消息。在实际的开发中呢,有两种情况是需要手动创建自动释放池的。第一个就是在多线程中,因为子线程中可能会使用便利构造器等方法来创建对象,那么这些对象的释放只能放在自动释放池中,主线程其实已经添加过自动释放池,在 main 函数里面。第二个就是如果一段代码里面(比如 for 循环)大量使用便利构造器创建对象,也需要手动添加自动释放池。 上述代码其实是存在问题的。当执行完第一行代码时, p1 的引用计数是 1. 第二行是创建了一个 autoreleasepool 。第三行代码向 p1 发送了 autorelease 消息,延迟 release ,即在出池的时候,把 p1 释放掉。第四行代码又创建了一个 autoreleasepool 。第五行代码再次向 p1 发送了 autorelease 消息。当代码执行到第六行的 “” 时,第二个自动释放池结束,这时 p1 引用技术减 1 , p1 所指向的内存(对象)的 retainCount 由 1 变为 0 ,内存被系统回收。代码执行到第七行时,最外层的自动释放池结束,再次向 p1 发送 release 消息,出现过度释放。 @autoreleasepool [p1 autorelease]; // 此时 p1 被加入 自动释放池 1 @autoreleasepool [p1 autorelease]; // 此时 p1 被加入自动释放池 2 // 此处,自动释放池 2 结束, p1 引用计数 -1 // 此处,自动释放池 1 结束,但是向已经被释放的对象 p1 发送了消息 4. 父类实现深拷贝时,子类如何实现深拷贝 ?父类没有实现深拷贝时,子类如何实现深度拷贝? 答:父类实现深拷贝之后,子类在重写的 copyWithZone 方法,先调用父类的 copyWithZone 方法,之后实现自己属性的拷贝。 如果父类没有实现深拷贝,子类除了需要对自己的属性进行拷贝,还要对父类的属性进行拷贝。 一、内存管理 ARC下的内存管理 ARC,全称叫AutomaticReference Counting,该机制从ios5开始开始导入。简单地说,就是代码中自动加入了retain/release。所以,其底层机制还是引用计数, 在你打开ARC时,你是不能使用retainrelease autorelease 操作的,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了,但是你需要在对象属性上使用weak 和strong, 其中strong就相当于retain属性,而weak相当于assign,基础类型还是使用assign。 strong关键字: strong 用来修饰强引用的属性;对应原来的retain。 该属性值对应 __strong 关键字,即该属性所声明的变量将成为对象的持有者。 weak关键字: weak 用来修饰弱引用的属性;对应原来的assign。 但是不同的是当对象被释放以后,对象自动赋值为nil;并且,delegate 和 Outlet 苹果推荐用 weak 属性来声明。同时,如上一回介绍的 iOS 5 之前的版本是没有 __weak 关键字的,所以 weak 属性是不能使用的。这种情况我们使用 unsafe_unretained。 IOS的ARC会导致的内存泄露问题和解决方案 iOS提供了ARC功能,很大程度上简化了内存管理的代码。 但使用ARC并不代表了不会发生内存泄露,使用不当照样会发生内存泄露。 下面列举两种ARC导致内存泄露的情况。 1,循环参照 A有个属性参照B,B有个属性参照A,如果都是strong参照的话,两个对象都无法释放。 这种问题常发生于把delegate声明为strong属性了。 2,死循环 如果某个ViewController中有无限循环,也会导致即使ViewController对应的view关掉了,ViewController也不能被释放。 这种问题常发生于animation处理。 5、内存中的栈和堆的区别是什么?哪些数据在栈上哪些数据在堆上? (1)管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生 memory leak。 (2)申请大小:能从栈获得的空间较小,堆是向高地址扩展的数据结构,是不连续的内存区域。堆的大小受限于计算机系统中 有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 (3)碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。 对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块 从栈中间弹出 (4)分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成 的,比如局部变量的分配。动态分配由 alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器 进行释放,无需我们手工实现。 (5)分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈 都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。 在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。 12.Objective-C如何对内存管理的,说说你的看法和解决方法? Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。 1. (Garbage Collection)自动内存计数:这种方式和java类似,在你的程序的执行过程中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它什么时候开始工作,怎样工作。你只需要明白,我申请了一段内存空间,当我不再使用从而这段内存成为垃圾的时候,我就彻底的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人需要消耗一定的资源,在携带设备里面,资源是紧俏商品所以iPhone不支持这个功能。所以“Garbage Collection”不是本入门指南的范围,对“Garbage Collection”内部机制感兴趣的同学可以参考一些其他的资料,不过说老实话“Garbage Collection”不大适合适初学者研究。 解决: 通过alloc – initial方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了. 2. (Reference Counted)手动内存计数:就是说,从一段内存被申请之后,就存在一个变量用于保存这段内存被使用的次数,我们暂时把它称为计数器,当计数器变为0的时候,那么就是释放这段内存的时候。比如说,当在程序A里面一段内存被成功申请完成之后,那么这个计数器就从0变成1(我们把这个过程叫做alloc),然后程序B也需要使用这个内存,那么计数器就从1变成了2(我们把这个过程叫做retain)。紧接着程序A不再需要这段内存了,那么程序A就把这个计数器减1(我们把这个过程叫做release);程序B也不再需要这段内存的时候,那么也把计数器减1(这个过程还是release)。当系统(也就是Foundation)发现这个计数器变成了0,那么就会调用内存回收程序把这段内存回收(我们把这个过程叫做dealloc)。顺便提一句,如果没有Foundation,那么维护计数器,释放内存等等工作需要你手工来完成。 解决:一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期. 3. (NSAutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机. 解决:是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放. 8)浅复制和深复制的区别 浅层复制:只复制指向对象的指针,而不复制引用对象本身。 深层复制:复制引用对象本身。 意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一 个内存资源,复制的只不过是是一个指针,对象本身资源 还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背 了我们复制拷贝的一个思想。深复制就好理解了,内存中存在了 两份独立对象本身。 用网上一哥们通俗的话将就是: 浅复制好比你和你的影子,你完蛋,你的影子也完蛋 深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。 深浅拷贝前提是:是实现NSCopying或者NSMutableCopying协议。 浅拷贝只是复制对象本身,对象的属性和包含的对象不做复制。 深拷贝则对对象本身复制,同时对对象的属性也进行复制。 深浅拷贝的本质区别是对象或者对象属性的内存地址是否一样,一样则为浅拷贝,不一样则为深拷贝。 Foundation框架支持复制的类,默认是浅拷贝。其中对Foundation中不可变的对象进行copy时作用相当于retain。 而如果是mutablecopy时,无论对象是否可变,副本是可变的,并且实现了真正意义上的copy。如果对可变对象进行copy, 副本对象是不可变的,同样是真正意义上的copy。 3.Objective-C堆和栈的区别? (1)管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生 memory leak。 (2)申请大小:能从栈获得的空间较小,堆是向高地址扩展的数据结构,是不连续的内存区域。堆的大小受限于计算机系统中 有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 (3)碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。 对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块 从栈中间弹出 (4)分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成 的,比如局部变量的分配。动态分配由 alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器 进行释放,无需我们手工实现。 (5)分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈 都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。 6.debug 和内存分析工具 众所周知,我们进行iOS开发,在Xcode调试程序时,分为两种方式,Debug和Release,在Target的Setting中相信大家应该看到很多选项都分为Debug和Release,方便我们分别设置,满足调试和发布的不同需求。 Release是发行版本,比Debug版本有一些优化,文件比Debug文件小 Debug是调试版本,Debug和Release调用两个不同的底层库。通俗点讲,我们开发者自己内部真机或模拟器调试时,使用Debug模式就好,等到想要发布时,也就是说需要大众客户使用时,需要build Release版本,具体区别如下: 一、Debug是调试版本,包括的程序信息更多 二、只有Debug版的程序才能设置断点、单步执行、使用TRACE/ASSERT等调试输出语句 三、Release不包含任何调试信息,所以体积小、运行速度快 内存分析工具: 1、Analyze 用XCode的Analyze就能分析到哪里有内存泄露 2、分析内存泄露不能把所有的内存泄露查出来,有的内存泄露是在运行时,用户操作时才产生的。那就需要用到Instruments了。 Leak工具可以很容易的统计所有内存泄漏的点,而且还可以显示在哪个文件,哪行代码有内存泄漏,这样定位问题比较容易,也比较方便;但是Leak在统计内存泄漏的时候会把autorelease方式的内存也统计进来; 所以我们在查找内存泄漏情况的时候,可以autorelease的情况忽略掉; 使用Xcode和Instruments调试解决iOS内存泄露 http://blog.csdn.net/totogo2010/article/details/8233565 3.属性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用? ①. readwrite 是可读可写特性;需要生成getter方法和setter方法时 ②. readonly 是只读特性 只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变 ③. assign 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时; ④. retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1; ⑤. copy 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时。 ⑥. nonatomic 非原子操作,决定编译器生成的setter getter是否是原子操作,atomic表示多线程安全,一般使用nonatomic 4.写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy)NSString *name - (void) setName:(NSString*) str [str retain]; [name release]; name = str; - (void)setName:(NSString *)str id t = [str copy]; [name release]; name = t; 8.Objective-C如何对内存管理的,说说你的看法和解决方法? Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。 ①.自动内存计数:这种方式和java类似,在你的程序的执行过程中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它什么时候开始工作,怎样工作。你只需要明白,我申请了一段内存空间,当我不再使用从而这段内存成为垃圾的时候,我就彻底的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人需要消耗一定的资源,在携带的移动设备里面,资源是紧俏商品所以iPhone不支持这个功能。 解决: 通过alloc – init方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了. ②. (Reference Counted)手动内存计数:就是说,从一段内存被申请之后,就存在一个变量用于保存这段内存被使用的次数,我们暂时把它称为计数器,当计数器变为0的时候,那么就是释放这段内存的时候。比如说,当在程序A里面一段内存被成功申请完成之后,那么这个计数器就从0变成1(我们把这个过程叫做alloc),然后程序B也需要使用这个内存,那么计数器就从1变成了2(我们把这个过程叫做retain)。紧接着程序A不再需要这段内存了,那么程序A就把这个计数器减1(我们把这个过程叫做release);程序B也不再需要这段内存的时候,那么也把计数器减1(这个过程还是release)。当系统(也就是Foundation)发现这个计数器变成了0,那么就会调用内存回收程序把这段内存回收(我们把这个过程叫做dealloc)。顺便提一句,如果没有Foundation,那么维护计数器,释放内存等等工作需要你手工来完成。 解决:一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期. ③. (NSAutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机. 解决:是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放. 9. 原子(atomic)跟非原子(non-atomic)属性有什么区别? ①. atomic提供多线程安全。是防止在写未完成的时候被另外一个线程读取,造成数据错误 ②. non-atomic:在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。 10.Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone: 11. 浅复制和深复制的区别? 答案:浅层复制:只复制指向对象的指针,而不复制引用对象本身。 深层复制:复制引用对象本身。 意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象本身资源 还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背了我们复制拷贝的一个思想。深复制就好理解了,内存中存在了 两份独立对象本身。 用网上一哥们通俗的话将就是: 浅复制好比你和你的影子,你完蛋,你的影子也完蛋 深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。 20.简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与alloc配对使用的方法是dealloc还是release,为什么?readwrite,readonly,assign,retain,copy,nonatomic 、atomic、strong、weak属性的作用? (OC第九讲 内存管理初级) OC使用了一种叫做引用计数的机制来管理对象,如果对一个对象使用了alloc、[Mutable]copy,retain,那么你必须使用相应的realease或者autorelease。也可以理解为自己生成的对象,自己持有。非自己生成的对象,自己也能持有。不在需要自己持有的对象时释放。非自己持有的对象无法释放。生成并持有对象<alloc,new,copy,mutableCopy等>,持有对象<retain>,释放对象<release>,废弃对象<dealloc>。readwrite(默认):可读可写,表示既有getter方法,也有setter方法。readonly:表示只有getter方法,没有setter方法。nonatomic:不考虑线程安全。atomic(默认):线程操作安全。strong(默认):ARC下和MRC下retain一样,weak(ARC下):和(MRC下)assign类似,区别是当weak指向的内存释放掉后自动置为nil,防止野指针。 unsafe_unretained声明一个若引用,但不会自动置为nil,可能会出现野指针。 线程安全下的setter和getter方法: -(NSString *)value @synchronized(self) return [[_value retain] autorelease]; - (void)setValue:(NSString *)aValue @synchronized(self) [aValue retain]; [_value release]; _value = aValue; 64.同一个工程中ARC和非ARC 的如何混合使用? 点击项目->TARGETS->Build Phases->Compile Sources中选择要改的.m,双击,在标签中添加: 1.如果是ARC项目,要加入非ARC的代码文件:-fobjc-arc 2.如果是非ARC,要加入ARC的代码:-fno-objc-arc 回车 43.内存中的栈和堆的区别是什么? 管理方式:对于栈来讲,是由编译器自动管理的,无需我们手动控制,对于堆来讲,释放工作有程序猿控制,这样就容易产生memory Leak 申请大小: 栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶上的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2M(也有的说1M,总之是编译器确定的一个常数),如果申请的空间超过了栈的剩余空间时候,就overflow。因此,能获得栈的空间较小。 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大笑受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大 碎片问题: 对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存快从栈中弹出 分配方式: 堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,他的动态分配由编译器进行释放,无需我们手工实现。 分配效率: 栈是机器系统提供的数据结构,计算机会在底层堆栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,他的机制是很复杂的。 3、iOS有没有垃圾回收? iOS没有垃圾回收机制。只有系统在应用程序退出的时候会在适当的时候回收所有的东西。ios5以后有arc,可以自动添加管理内存的代码,但与垃圾回收不是一个东西。一个需要自己管理,一个不需要。 其实也可以垃圾回收,但是这样的话对性能有影响,而且手机不像电脑,对性能要求严格,所以苹果果断没有用垃圾回收。把内存的烦恼留程序员,但为了照顾程序员,又发明了arc。 iOS开发系列—Objective-C之内存管理 http://www.cnblogs.com/kenshincui/p/3870325.html 11. 内存池的概念详细解释一下 内存池 : 内存池就是用来存放当前创建的多有对象的空间位置 , 当我们在创建一个实例对象以后系统会自动的把内容放到内存池中 , 当我们在使用这些对象的时候就会在内存中自动的去执行 retain,copy,release 操作 , 当实例对象的引用计数为 0 时就会自动的执行 dealloc 方法来销毁实例对象 . 在内存池中也会有 runloop 作用是来监察每个实例对象的引用计数 . 31.如何优化内存? (1). 用ARC管理内存 ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露。它自动为你管理retain和release的过程,除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存。 (2). 在正确的地方使用reuseIdentifier 一个开发中常见的错误就是没有给UITableViewCells, UICollectionViewCells,甚至是UITableViewHeaderFooterViews设置正确的reuseIdentifier。 (3).尽量把views设置为透明 如果你有透明的Views你应该设置它们的opaque属性为YES。 原因是这会使系统用一个最优的方式渲染这些views。如果设为YES,渲染系统就认为这个view是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为NO,渲染系统正常地和其它内容组成这个View。默认值是YES。 (4).避免过于庞大的XIB 当你加载一个XIB的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。 (5).不要阻塞主线程 永远不要使主线程承担过多。因为UIKit在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。 一直使用主线程的风险就是如果你的代码真的block了主线程,你的app会失去反应。 大部分阻碍主进程的情形是你的app在做一些牵涉到读写外部资源的I/O操作,比如存储或者网络。 (6). 在Image Views中调整图片大小 如果要在`UIImageView`中显示一个来自bundle的图片,你应保证图片的大小和UIImageView的大小相同。在运行中缩放图片是很耗费资源的,特别是`UIImageView`嵌套在`UIScrollView`中的情况下。 如果图片是从远端服务加载的你不能控制图片大小,比如在下载前调整到合适大小的话,你可以在下载完成后,最好是用background thread,缩放一次,然后在UIImageView中使用缩放后的图片。 (7). 选择正确的Collection 学会选择对业务场景最合适的类或者对象是写出能效高的代码的基础。当处理collections时这句话尤其正确。 一些常见collection的总结: · Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢,插入/删除很慢。 ·Dictionaries: 存储键值对。用键来查找比较快。 · Sets: 无序的一组值。用值来查找很快,插入/删除很快。 (8). 打开gzip压缩 大量app依赖于远端资源和第三方API,你可能会开发一个需要从远端下载XML, JSON, html或者其它格式的app。 问题是我们的目标是移动设备,因此你就不能指望网络状况有多好。一个用户现在还在edge网络,下一分钟可能就切换到了3G。不论什么场景,你肯定不想让你的用户等太长时间。 减小文档的一个方式就是在服务端和你的app中打开gzip。这对于文字这种能有更高压缩率的数据来说会有更显著的效用。 好消息是,iOS已经在NSURLConnection中默认支持了gzip压缩,当然AFNetworking这些基于它的框架亦然。像Google App Engine这些云服务提供者也已经支持了压缩输出。 (9). 重用和延迟加载(lazy load) Views 更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了很多view在 UIScrollView里边的app更是如此。 这里我们用到的技巧就是模仿`UITableView`和`UICollectionView`的操作:不要一次创建所有的subview,而是当需要时才创建,当它们完成了使命,把他们放进一个可重用的队列中。 这样的话你就只需要在滚动发生时创建你的views,避免了不划算的内存分配。 创建views的能效问题也适用于你app的其它方面。想象一下一个用户点击一个按钮的时候需要呈现 一个view的场景。有两种实现方法: 1. 创建并隐藏这个view当这个screen加载的时候,当需要时显示它; 2. 当需要时才创建并展示。 每个方案都有其优缺点。用第一种方案的话因为你需要一开始就创建一个view并保持它直到不再使用,这就会更加消耗内存。然而这也会使你的app操作更敏感因为当用户点击按钮的时候它只需要改变一下这个view的可见性。 第二种方案则相反-消耗更少内存,但是会在点击按钮的时候比第一种稍显卡顿。 (10). Cache, Cache, 还是Cache! 一个极好的原则就是,缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。 我们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果,比如UITableView的行高。 NSURLConnection默认会缓存资源在内存或者存储中根据它所加载的HTTP Headers。你甚至可以手动创建一个NSURLRequest然后使它只加载缓存的值。 (11).权衡渲染方法 在iOS中可以有很多方法做出漂亮的按钮。你可以用整幅的图片,可调大小的图片,uozhe可以用CALayer, CoreGraphics甚至OpenGL来画它们。 当然每个不同的解决方法都有不同的复杂程度和相应的性能。 简单来说,就是用事先渲染好的图片更快一些,因为如此一来iOS就免去了创建一个图片再画东西上去然后显示在屏幕上的程序。问题是你需要把所有你需要用到的图片放到app的bundle里面,这样就增加了体积–这就是使用可变大小的图片更好的地方了:你可以省去一些不必要的空间,也不需要再为不同的元素(比如按钮)来做不同的图。 然而,使用图片也意味着你失去了使用代码调整图片的机动性,你需要一遍又一遍不断地重做他们,这样就很浪费时间了,而且你如果要做一个动画效果,虽然每幅图只是一些细节的变化你就需要很多的图片造成bundle大小的不断增大。 总得来说,你需要权衡一下利弊,到底是要性能能还是要bundle保持合适的大小。 (12).处理内存警告 一旦系统内存过低,iOS会通知所有运行中app。如果你的app收到了内存警告,它就需要尽可能释 放更多的内存。最佳方式是移除对缓存,图片object和其他一些可以重创建的objects的strong references. 幸运的是,UIKit提供了几种收集低内存警告的方法: · 在app delegate中使用`applicationDidReceiveMemoryWarning:`的方法 · 在你的自定义UIViewController的子类(subclass)中覆盖`didReceiveMemoryWarning` · 注册并接收UIApplicationDidReceiveMemoryWarningNotification的通知 一旦收到这类通知,你就需要释放任何不必要的内存使用。 例如,UIViewController的默认行为是移除一些不可见的view,它的一些子类则可以补充这个方法,删掉一些额外的数据结构。一个有图片缓存的app可以移除不在屏幕上显示的图片。 (13).重用大开销对象 一些objects的初始化很慢,比如NSDateFormatter和NSCalendar。然而,你又不可避免地需要使用它们,比如从JSON或者XML中解析数据。 想要避免使用这个对象的瓶颈你就需要重用他们,可以通过添加属性到你的class里或者创建静态变量来实现。 注意如果你要选择第二种方法,对象会在你的app运行时一直存在于内存中,和单例(singleton)很相似。 还需要注意的是,其实设置一个NSDateFormatter的速度差不多是和创建新的一样慢的!所以如果你的app需要经常进行日期格式处理的话,你会从这个方法中得到不小的性能提升。 (14). 减少使用Web特性 UIWebView很有用,用它来展示网页内容或者创建UIKit很难做到的动画效果是很简单的一件事。 但是你可能有注意到UIWebView并不像驱动Safari的那么快。这是由于以JIT compilation为特色的Webkit的Nitro Engine的限制。 所以想要更高的性能你就要调整下你的HTML了。第一件要做的事就是尽可能移除不必要的 javascript,避免使用过大的框架。能只用原生js就更好了。 另外,尽可能异步加载例如用户行为统计script这种不影响页面表达的javascript。 最后,永远要注意你使用的图片,保证图片的符合你使用的大小。使用Sprite sheet提高加载速度和节约内存。 (15). 优化Table View Table view需要有很好的滚动性能,不然用户会在滚动过程中发现动画的瑕疵。 为了保证table view平滑滚动,确保你采取了以下的措施: · 正确使用`reuseIdentifier`来重用cells · 尽量使所有的view opaque,包括cell自身 · 避免渐变,图片缩放,后台选人 · 缓存行高 · 如果cell内现实的内容来自web,使用异步加载,缓存请求结果 · 使用`shadowPath`来画阴影 · 减少subviews的数量 · 尽量不适用`cellForRowAtIndexPath:`,如果你需要用到它,只用一次然后缓存结果 · 使用正确的数据结构来存储数据 · 使用`rowHeight`, `sectionFooterHeight`和 `sectionHeaderHeight`来设定固定的高,不要请求delegate (16). 使用Autorelease Pool `NSAutoreleasePool`负责释放block中的autoreleased objects。一般情况下它会自动被UIKit调用。但是有些状况下你也需要手动去创建它。 假如你创建很多临时对象,你会发现内存一直在减少直到这些对象被release的时候。这是因为只有当UIKit用光了autorelease pool的时候memory才会被释放。好消息是你可以在你自己的 @autoreleasepool里创建临时的对象来避免这个行为: NSArray *urls = <# An array of file URLs #>; for(NSURL *url in urls) @autoreleasepool NSError *error; NSString *fileContents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error]; /* Process the string, creating and autoreleasing more objects. */ 这段代码在每次遍历后释放所有autorelease对象 (17). 选择是否缓存图片 常见的从bundle中加载图片的方式有两种,一个是用`imageNamed`,二是用`imageWithContentsOfFile`,第一种比较常见一点。 既然有两种类似的方法来实现相同的目的,那么他们之间的差别是什么呢? `imageNamed`的优点是当加载时会缓存图片。`imageNamed`的文档中这么说:这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应 的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。 相反的,`imageWithContentsOfFile`仅加载图片。 如果你要加载一个大图片而且是一次性使用,那么就没必要缓存这个图片,用 `imageWithContentsOfFile`足矣,这样不会浪费内存来缓存它。 然而,在图片反复重用的情况下`imageNamed`是一个好得多的选择 6.OC 中 alloc,retain,copy,release,autorelease 的含义是什么?,请写出下面函数中带有标号的执行后 tracker 的引用计数。 int main(int argc, const char *argv[]) NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; RetainTracker *tracker; tracker = [RetainTracker new]; (1) RetainTracker *otherTrecker = [tracker copy]; (2) [tracker retain]; (3) [tracker autorelease]; (4) [tracker release]; (5) [otherTrecker release]; (6) [pool release]; return 0; alloc 初始化开辟内存空间,自己的 retainCount 为一 ; retain 是把该对象的 retainCount 加一 ; copy 是拷贝一个新的对象 , 像 alloc, 使新对象的 retainCount 值为一 . 而 release 和 autorelease 是使 retainCount 减一的操作 , 你自己加一的操作用完了一定要减一就行了 , 以上是关于项目中关于内存管理的问题的主要内容,如果未能解决你的问题,请参考以下文章Java中关于内存泄漏出现的原因以及如何避免内存泄漏(超详细版)