流程图如下
上面是我针对内存管理的了解,进行了一个简单的用流程的方式加以概括。下面是详细的说明一下,在面试过程中,针对内存管理,面试官经常会问到哪些问题进行剖析。
之前面试的时候,面试官都会问的问题是,你对内存管理了解吗?简单说一下,而我就会将上面这个图设计到的点全都用手绘的形式给他讲一遍。这样你就占据了主导位置。哈哈哈
内存管理分为 mrc和arc两种管理方式。
我们先从mrc说起。
MRC
规则:谁创建,谁释放,谁引用,谁管理的原则。
alloc, init,new,copy-------->release
当一个对象的retainCount = 0的时候,就会调用dealloc方式,将对象释放掉。释放内存的并不是release,而是dealloc。
retain是保持的意思,给一个对象发送retian消息,就意味着保持这个对象。它的retainCount+1。具体我用一张流程图说明:
针对上面这些内容,其实可以引申出很多面试题。举个例子🌰:
什么情况下使用weak关键字,相比assgin有什么不同?
1、delegate.因为在arc中,有可能会出现循环引用。
2、自身已经对它进行一次强引用,没有必要再强引用一次,比如IBOutlet控件属性。
不同点
1、weak为属性设置新值的时候,设置方法既不保留新值,也不释放旧值。在遭到摧毁时,属性值也会被清空。assgin的设置方法只针对基本数据类型等简单的赋值操作。
2、assgin可以用非OC对象,weak必须用于oc对象。
runtime如何实现weak属性、实现原理
这个会在后续的篇幅中重点介绍
@property中有哪些属性关键字?/@property后面可以有哪些修饰符?
或者换一个问法:
属性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?
1). readwrite 是可读可写特性;需要生成getter方法和setter方法时
2). readonly 是只读特性 只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变
3). assign 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;
4). retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1;
5). copy 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时。
6).nonatomic 非原子操作,决定编译器生成的setter getter是否是原子操作,atomic表示多线程安全,一般使用nonatomic
weak属性在dealloc中置nil么?
不需要。
在ARC中环境无论是强指针还是弱指针都无需在dealloc设置为Nil,ARC会自动帮我们处理。
在属性所指的对象遭到摧毁时,属性值也会清空。
- (void)setObject:(NSObject *)object{
objc_setAssociatedObject(self, @"object", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
关于copy关键字的深浅拷贝等面试题,会在后面提到。我们继续说一下内存管理。
ARC
还是上面那张关于引用计数的流程图。
autorelease
实现、本质、RunLoop相关
·App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
·第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
·第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
·在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
也就是说AutoreleasePool创建是在一个RunLoop事件开始之前(push),AutoreleasePool释放是在一个RunLoop事件即将结束之前(pop)。
AutoreleasePool里的Autorelease对象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease对象的释放是在AutoreleasePool释放时。
具体见这篇文章,非常清楚
AutoreleasePool底层实现原理剖析
问:
1、谈谈你对自动释放池的理解
2、多层自动释放池嵌套的对象在哪一层释放。
自动释放池是oc提供的一种自动回收的机制,具有延迟释放的特性,即当我们创建了一个对象,并把他加入到了自动释放池中时,他不会立即被释放,会等到一次runloop结束或者作用域超出{}或者超出[pool release]之后再被释放。
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对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放.
内存管理的几条原则时什么?按照默认法则.那些关键字生成的对象需要手动释放?在和property结合的时候怎样有效的避免内存泄露?
答:谁申请,谁释放
遵循Cocoa Touch的使用原则;
内存管理主要要避免“过早释放”和“内存泄漏”,对于“过早释放”需要注意@property设置特性时,一定要用对特性关键字,对于“内存泄漏”,一定要申请了要负责释放,要细心。
关键字alloc 或new 生成的对象需要手动释放;
设置正确的property属性,对于retain需要在合适的地方释放,