1、ARC的实现原理
ARC的实现过程就是编译器在编译期间对代码在合适的位置上自动加上release和autorelease;遵循的原则如下:
a、对于每一个持有的对象,都会在合适的地方加入release来释放对b象;
b、对于在方法内部创建的对象,都会在方法末尾加入release来释放对象;
c、对于类的对象和实例变量,系统会在dealloc内被释放
2、下面关于Objective-C的内存描述错误的是
A 当使用ARC来管理内存时,代码中不可以出现autorelease
B autoreleasepool 在 drain 的时候会释放在其中分配的对象
C 当使用ARC来管理内存时,在线程中大量分配对象而不用autoreleasepool则可能会造成内存泄露
D 在使用ARC的项目中不能使用NSZone
答案:A;理由:ARC中编译器会在编译期间在合适的位置加上内存管理的代码,只是在我们的源码中看不到而已;
3、如果要在ARC工程中使用MRC,该怎么做?
可以在工程配置文件中添加-fno-objc-arc
4、什么情况下使用weak?与assgin有什么不同?
a、在ARC中,为了避免对象的循环引用,可以用weak来修饰;比如:delegate
b、如果对象已经是强引用了,此时没必要在做抢引用,可以用weak来修饰;比如:IBOutlet
c、weak是ARC中才出现的
d、weak只能修饰OC对象,assgin可以修饰非OC对象
e、weak既不会持有对象,也不会释放对象(与assgin类型);但对象在释放后,weak修饰的会自动设置为nil;而assgin修饰的不会,会变为野指针;
5、调用对象的release方法会销毁吗?
看情况而定,release只是将对象的引用计数-1;如果对象在release之后,内存的引用计数刚好为0,则会调用对象的dealloc方法释放对象;
6、Objective-C对象的内存布局是怎么样的?
先是isa指针,然后是父类的实例变量存放在子类的成员变量之前
7、什么时候需要在程序中创建内存池?如果我们不创建内存池,是否有内存池供我们使用?
用户自己创建的数据线程,需要创建该线程的内存池;
界面线程有自己的内存池
8、autoreleasepool如何实现?
autoreleasepool是基于队列数组实现的;常用的方法:objc_autoreleasepoolPush、objc_autoreleasepoolPop、objc_autorelease
9、Objective-C使用什么机制管理内存
通过内存引用计数器(retainCount)来管理的;当runloop执行完一次,就会坚持对象的引用计数,如果计数刚好为0;则表示该对象不需要使用了,可以释放了;
10、为什么要进行内存管理
因为移动设备的内存空间有限,当程序内存达到一定大小,系统会发出内存警告;当内存达到更大的值,程序会立即崩溃,从而影响用户体验;所以需要进行内存管理
11、内存管理的范围
所有继承自NSObject的对象,对基本数据类型无效;
12、内存管理的原则
a、只要有人在使用对象,那么对象就不会被释放
b、如果你想使用对象,那么就让对象的引用计数+1
c、当你不想使用对象时,就让对象的引用计数-1
d、谁创建、谁释放;比如通过alloc、new、copy创建
e、谁retain,谁release
13、内存管理的研究对象
野指针:对象没有创建或者指向空间地址已经被释放;使用野指针调用对象方法,会导致异常程序崩溃,所以通常在对象release之后,需要把对象的地址清空,置为nil;OC中没有nil异常,调用[nil retain]不会异常
内存溢出:没有配对释放,不符合内存管理法则;提前是赋值nil或者清空,导致release无效
空指针:指针赋值为空nil
14、如何判断对象已经被销毁
重写dealloc方法,对象销毁时,系统胡自动调用,并调用[super dealloc],
15、如果retainCount=0,在使用retain是否可以是对象复活
已经被释放的对象无法复活
16、对象与对象之间的关系
继承关系
组合关系(强包含关系)
依赖关系(对象作为方法的参数)
17、对象组合关系中,如何确保对象不被提前释放?组合关系中的内存泄露有哪些?
在set方法中调用对象的retain方法;
组合关系中的内存泄露:
a、set方法没有retain
b、没有release旧对象
c、set中没有判断传入参数是否是同一个对象
18、正确重写set方法
判断对象是否为同一个
release旧对象
retain新对象
19、分别描述内存管理要点、autorelease、release、NSAutoreleasePool?并说明autorelease是什么时候被release的?简述什么时候由你负责释放对象,什么时候不由你释放?[NSAutoreleasePool release]和[NSAutoreleasePool drain]有什么区别?
内存管理要点:Objective-C 使用引用计数机制(retainCount)来管理内存。
内存每被引用一次,该内存的引用计数+1,每被释放一次引 用计数-1。
当引用计数 = 0 的时候,调用该对象的 dealloc 方法,来彻底从内存中删除该对象。
alloc,allocWithZone,new(带初始化)时:该对象引用计数 +1;
retain:手动为该对象引用计数 +1;
copy:对象引用计数 +1;//注意copy的OC数据类型是否有mutable,如有为深拷贝,新对象计数为1,如果没有,为浅拷贝,计数+1
mutableCopy:生成一个新对象,新对象引用计数为 1;
release:手动为该对象引用计数 -1;
autorelease:把该对象放入自动释放池,当自动释放池释放时,其内的对象引用计数 -1。
NSAutoreleasePool: NSAutoreleasePool是通过接收对象向它发送的autorelease消息,记录该对象的release消息,当自动释放池被销毁时,会自动向池中的对象发送release消息。
autorelease 是在自动释放池被销毁,向池中的对象发送release
只能释放自己拥有的对象。
区别是:在引用计数环境下(在不使用ARC情况下),两者基本一样,在GC(垃圾回收制)环境下,release 是一个no-op(无效操作),所以无论是不是GC都使用drain
面试中内存管理,release和autorelease的含义?这里尤其要强调下autorelease,它引申出自动释放池,也能引申出Run loop!
20、自动释放池是什么?是如何工作的?
自动释放池:是存放多个对象类型的指针变量
存入自动释放池的对象,当自动释放池被销毁的时候,会对池内的所有对象进行释放;
可以通过调用对象的autorelease方法,将对象加入到自动释放池
自动释放池的释放是由当前线程的runloop决定的,在runloop迭代结束时释放,之所以可以释放是因为在每个runloop的迭代周期都加入了自动释放池的push和pop
多次调用对象的autorelease,会导致野指针
21、自动释放池何时释放
通过observer监听runloop的状态,一旦runloop进入休眠状态,系统会释放自动释放池
22、ARC通过什么方式帮助开发者管理内存
ARC是在程序编译与运行期间帮助开发者管理内存,由系统自动添加retain、release和autorelease
23、设计一个简单的图片内存缓存器
主要是讲一下常见的缓存算法有:
FIFO(先进先出队列):如果一个数据最早进入队列,则应该最早被移除
a、新加入的数据插入队列尾部,顺序移动
b、当需要淘汰数据时,直接移除队列头部的数据
最常见的实现方法就是采用队列Queue来实现,如下图:
LRU(最近最少使用,淘汰最长时间未使用的对象):如果一个数据最近被访问过,那么将来访问的机率会更高;如下图:
a、新加入的数据,放在链表的头部
b、最近使用的数据,移动到链表的头部
c、当链表满了,移除链表尾部的数据
LRU采用的是双向链表+map的方式实现,之所以采用双向链表,是因为单向链表,如果要删除节点,需要从表头开始变了查找,时间的复杂度为O(n),如果采用双向链表,则可以直接改变节点的前驱指针指向来进行删除;采用map来保存key,value是为了在O(logN)的时间快速找到节点,对应get操作;
LFU(最不经常使用,淘汰一段时间内使用次数较少的对象):如果一个数据在最近一段时间内使用的次数很少,那么在将来的一段时间被使用的可能性也会很小;如下图:
a、新加入的数据,插入到队列的尾部
b、最近使用的数据,引用计数+1,重新排序队列
c、当需要淘汰数据时,直接删除队列尾部数据
24、最常出现内存循环引用的场景有哪些?
定时器:当定时器作为某个类的成员变量,而定时器需要指定self为target,这个时候就容易引起循环引用(self->timer->self);如果定时器一直处于validate,则其引用计数一直大于0,所以在使用完定时器之后,需要调用定时器的invalidate方法
block使用:;block在copy的时候会对block里面的对象进行强引用(ARC)或者retainCount+1(非ARC),如果使用不当也容易导致循环引用;最常见的表现就是:某个类将block作为自己的属性变量,然后在block里面又调用的类本身,从而导致循环引用(self->block->self)
delegate:规避代理的循环引用,可以使用assgin(非ARC)和weak(ARC)来修饰delegate解决
25、对象添加到通知中心中,当通知中心发通知时,这个对象却已经被释放了,可能会出现什么问题?
通知是多对多的关系,主要用于跨模块传值;当对象加入到通知中心后,如果在对象被销毁前没有从通知中心移除,当发送通知时就会发生崩溃。所以一定要在对象释放前移除通知
26、ARC下不显式指定任何属性关键字时,默认的关键字都有哪些?
对于基本数据类型,默认关键字是:atomic、readwrite、assgin
对于一般Objective-C的类型,默认关键字是:atomic、readwrite、strong
27、写出下面程序段的输出结果
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"a string value" forKey:@"akey"];
NSLog(@"%@", [dict objectForKey:@"akey"]);
[dict release];
结果:打印出“a string value”的字符串,然后发生崩溃;原因在于利用便利构造器生成的对象,不需要我们手动管理内存,如果手动调用release会造成内存过渡释放
28、请写出以下代码的执行结果
NSString * name = [ [ NSString alloc] init ];
name = @”Habb”;
[ name release];
结论:打印输出结果为“Habb”,在release前后打印都有结果;但是会导致内存泄露,原因在于之前的内存会变成野指针,后面的release不能释放之前创建的内存
29、怎么保证多人开发进行内存泄露的检查
可以使用Analyze静态分析
多人开发最好使用ARC模式来管理内存
30、非自动内存管理情况下怎么做单例模式。
a、声明一个类的静态变量,并置为nil;
b、实现一个类的静态方法,并判断当且仅当类的实例对象为nil时才创建新的实例
c、实现NSCopying协议,实现allocWithZone确保通过alloc、init等方式直接获取实例对象时不会产生另外的对象
d、重写release、autorelease、retain和retainCount方法,以确保单例的状态
e、在多线程环境中,可以使用@synchronized或者GCD来确保静态实例被正确的创建和初始化
31、内存泄露与内存溢出有什么关系?
内存泄露:只一个对象被创建后,没有有效的释放对象,导致对象的内存一直处于占用状态,从而导致内存泄露;长期大量的内存泄露会导致内存溢出
32、[NSArray arrayWithobject:] 这个方法添加对象后,需要对这个数组做释放操作吗?
不需要。因为OC中提供大量的简便构造函数,编译器会在编译阶段自动给加上autorelease,所以这类的内存统一由系统自动管理,不需要手动释放
33、自动释放池的底层实现原理
自动释放池是基于栈实现的,当有新的对象autorelease,会被加入到自动释放池的栈顶;当前线程中每一轮runloop到结束的时候,自动释放池会被释放,然后自动释放池中的所有对象都会被release一次