技术iOS 进阶iOS开发

iOS面试题-内存管理

2018-02-05  本文已影响1812人  Liberalism
1.什么是 ARC?

ARCiOS 5 引入的内存管理新功能 -- 自动引用计数 。它的工作原理大致是这样:当我们编译源码时,编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,来添加相应的引用计数操作代码。所以,ARC 是工作在编译期的一种技术方案。

这样的好处是:编译之后,ARC 与非 MRC 代码是没有什么差别的,所以二者可以在源码中共存。实际上,你可以通过编译参数 -fno-objc-arc 来关闭部分源代码的 ARC 特性。由于 ARC 能够深度分析每一个对象的生命周期,它能够做到比 MRC 更加高效。

例如在一个函数中,对一个对象刚开始有一个引用计数 +1 的操作,之后又紧接着有一个 -1 的操作,那么编译器就可以把这两个操作都优化掉。

2 ARC 的核心思想?
3 ARC 在使用时应该遵循的原则?
4 ARC 在编译时做了哪些工作?
5 ARC 在运行时做了哪些工作?
_myPerson = [ECOPerson personWithName:@“Bob”]; 
ECOPerson * tmp = [ECOPerson personWithName:@“Bob”]; 
_myPerson = [tmp retain];
6 函数返回一个对象时,会对对象autorelease么?为什么?autorelease是什么时候释放的?

会对对象 autorelease,因为需要在稍后释放对象,从而给调用者留下足够长的时间,使其可以在需要时先保留返回值。此方法可以保证对象在跨越方法调用边界时一定存活。

除非你有自己的自动释放池,否则这个时机就是当前线程,当前事件循环结束时,就是 RunLoop 结束时(observer -> beforeWaiting)。

// 情况一:
@autoreleasepool {
    NSObject * obj = [NSObject new];
    [obj autorelease];
    NSLog(@"%d",[obj retainCount]); //1
}
// 情况二:
NSObject * obj = [NSObject new];
[obj autorelease];
[obj autorelease];
NSLog(@"%d",[obj retainCount]); 
NSLog(@"%d",[obj retainCount]);   

// 崩溃
// 情况三:
NSObject * obj;
@autoreleasepool {
        obj = [NSObject new];
        [obj autorelease];
        NSLog(@"%d",[obj retainCount]);  // 1
  }
//crash  出了大括号就已经被销毁了
NSLog(@"%d",[obj retainCount]);
7 为什么已经有了 ARC ,还需要 @autoreleasePool?

提到 OCRC,首先要横向对比一下 AndroidGC(垃圾回收机制)GC 的内存回收是集中式回收(定期回收),而 RC 的回收是伴随整个运行时的,所以 android 机器有种 时“卡”时“流畅” 的感觉,而 iOS 总体比较均匀,缺乏像 GC 的集中式回收内存的类似机制,所以猜测 Pool的产生也是弥补 RC 的这一不足,在 RC 基础上进行内存优化的一种手段。

8 简要阐述内存相关的关键字?
Strong

Strong 修饰符表示指向并持有该对象,其修饰对象的引用计数会加1。该对象只要引用计数不为0就不会被销毁。当然可以通过将变量强制赋值 nil 来进行销毁。

Weak

weak 修饰符指向但是并不持有该对象,引用计数也不会加1。在 Runtime 中对该属性进行了相关操作,无需处理,可以自动销毁。weak用来修饰对象,多用于避免循环引用的地方。weak 不可以修饰基本数据类型。

assign

assign主要用于修饰基本数据类型,
例如NSIntegerCGFloat,存储在栈中,内存不用程序员管理。assign是可以修饰对象的,但是会出现问题。

copy

copy关键字和 strong类似,copy 多用于修饰有可变类型的不可变对象上 NSString,NSArray,NSDictionary上。

__unsafe_unretain

__unsafe_unretain 类似于 weak ,但是当对象被释放后,指针已然保存着之前的地址,被释放后的地址变为 僵尸对象,访问被释放的地址就会出问题,所以说他是不安全的。

__autoreleasing

将对象赋值给附有 __autoreleasing修饰的变量等同于 ARC 无效时调用对象的 autorelease 方法,实质就是扔进了自动释放池。

9 说一下什么是悬垂指针?什么是野指针?
悬垂指针

指针指向的内存已经被释放了,但是指针还存在,这就是一个 悬垂指针 或者说 迷途指针

野指针

没有进行初始化的指针,其实都是 野指针

10 内存管理默认的关键字?
MRC
@property (atomic,readWrite,retain) NSString *name;
ARC
@property (atomic,readWrite,strong) NSString *name;
10 __weak 和 __unsafe_unretain 的区别?

__weak__unsafe_unretain升级版,__unsafe_unretain 在指向的内存地址销毁后,指针本身并不会自动销毁,这也就造成了野指针,之后容易造成 Crash。__weak 在指向的内存销毁后,可以将指针变量置为 nil,这样更加安全。

11 __weak 修饰的变量在地址被释放后,为何被置为 nil?

Runtime 中专门维护了一个用于存储 weak指针变量的 weak 表,这实际上是一个 Hash 表。这个表 keyweak指针 所指向的内存地址,value 是指向这个内存地址的所有 weak指针,实际上是一个数组。

过程可以总结为3步

12 为什么在 MRC 已经有 __weak 的情况下,还需要 _unsafe_unretain。?
13如何打破循环引用?
14能不能用 assign 修饰 Nsobject 类型?

也可以,但有可能出问题。

使用 assign 修饰 Nsobject 类型,赋值之后会被立即释放,对应的属性也就变成了野指针。

运行时跑到属性有关操作会直接崩溃掉。

15 内存中的5大区都是什么?
16 autoReleasePool 什么时候释放?

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observerorder2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

17 简要说一下自动引用计数的数据存储结构?

可以参考这篇文章 : 黑幕背后的Autorelease

上一篇 下一篇

猜你喜欢

热点阅读