iOS | 面试知识整理 - 内存管理 (五)
2019-08-17 本文已影响0人
Leon_520
前言:
最近公司项目不怎么忙, 闲暇时间把iOS 在面试中可能会遇到的问题整理了一番, 一部分题目是自己面试遇到的,一部分题目则是网上收录的, 方便自己巩固复习, 也分享给大家! 知识点比较多,比较杂,这里做了分类,下面是分类链接地址;
面试知识点整理 - 目录:
iOS | 面试知识整理 - OC基础 (一)
iOS | 面试知识整理 - OC基础 (二)
iOS | 面试知识整理 - OC基础 (三)
iOS | 面试知识整理 - UI 相 关 (四)
iOS | 面试知识整理 - 内存管理 (五)
iOS | 面试知识整理 - 多 线 程 (六)
iOS | 面试知识整理 - 网络相关 (七)
iOS | 面试知识整理 - 数据持久化 (八)
iOS | 面试知识整理 - Swift 基础 (九)
iOS | 面试知识整理 - 内存管理(五)
1.什么是内存泄漏?
- 内存泄漏指动态分配内存的对象在使用完后没有被系统回收内存,导致对象始终占有着内存,属于内存管理出错, (例如一个对象或者变量使用完成后没有释放,这个对象一直占用着内存),一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
2. 什么是僵尸对象?
- 已经被销毁的对象(不能再使用的对象),内存已经被回收的对象。一个引用计数器为0对象被释放后就变为了僵尸对象;
3. 野指针
-
野指针又叫做'悬挂指针', 野指针出现的原因是因为指针没有赋值,或者指针指向的对象已经释放了, 比如指向僵尸对象;野指针可能会指向一块垃圾内存,给野指针发送消息会导致程序崩溃
比如:NSObject *obj = [NSObject new]; [obj release]; // obj 指向的内存地址已经释放了, obj 如果再去访问的话就是野指针错误了. 野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS,因为你访问了一块已经不属于你的内存。
4. 什么是空指针?
- 空指针不同于野指针,他是一个没有指向任何内存的指针,空指针是有效指针,值为
nil,NULL,Nil,0
等,给空指针发送消息不会报错,不会响应消息;
5. OC对象的内存管理机制?
在iOS中,使用引用计数来管理OC对象的内存
- 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
- 调用
retain
会让OC对象的引用计数+1,调用release
会让OC对象的引用计数-1
内存管理的经验总结
- 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
- 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
6. OC中有GC垃圾回收机制吗?,iPhone上GC吗?
- 垃圾回收(GC),就是程序中用于处理废弃不用的内存对象的机制,防止内存泄露
- OC本身是支持垃圾回头得,不过只支持MAC OSX平台, iOS 平台不支持
7.在OC中与 Alloc 语义相反的是 release 还是 dealloc?
- alloc 与 dealloc 语义相反,alloc 是创建变量,dealloc是释放变量
- retain 与 release 语义相反, retain 保留一个对象,引用计数器+1, release 使引用计数器 -1;
8.什么是内存溢出?
- 当程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个int,但给它存了long才能存下的数,那就是内存溢出。
9.内存区域分布
在iOS开发过程中,为了合理的分配有限的内存空间,将内存区域分为五个区,由低地址向高地址分类分别是:代码区、常量区、全局静态区、堆、栈。
- 代码段 -- 程序编译产生的二进制的数据
- 常量区 -- 存储常量数据,通常程序结束后由系统自动释放
- 全局静态区 -- 全局区又可分为未初始化全局区:.bss段和初始化全局区:data段。全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,在程序结束后有系统释放。
- 堆(heap) -- 程序运行过程中,动态分配的内存
- 栈(stack) -- 存放局部变量,临时变量
10.堆区和栈取的区别
-
按管理方式分
- 对于栈来讲,是由系统编译器自动管理,不需要程序员手动管理
- 对于堆来讲,释放工作由程序员手动管理,不及时回收容易产生内存泄露
-
按分配方式分
- 堆是动态分配和回收内存的,没有静态分配的堆
- 栈有两种分配方式:静态分配和动态分配
- 静态分配是系统编译器完成的,比如局部变量的分配
- 动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,它的动 态分配也由系统编译器进行释放,不需要程序员手动管理
11.怎么保证多人开发进行内存泄露的检查.
- 使用Analyze进行代码的静态分析
- 为避免不必要的麻烦, 多人开发时尽量使用ARC
- 使用leaks 进行内存泄漏检测
- 使用一些三方工具
12.block在ARC中和MRC中的用法有什么区别,需要注意什么?
- 对于没有引用外部变量的Block,无论在ARC还是非ARC下,类型都是 NSGlobalBlock,这种类型的block可以理解成一种全局的block,不 需要考虑作用域问题。同时,对他进行Copy或者Retain操作也是无效的
- 都需要应注意避免循环引用,ARC 下使用__weak 来解决,MRC下使用__Block 来解决;
13.OC 如何对内存管理解决方法?
Objective-C的内存管理主要有三种方式 自动内存管理、手动内存管理、自动释放池。
- 自动内存计数
- 手动内存计数:
- 自动释放池:
14.ARC 都帮我们做了什么?
- LLVM + Runtime 会为我们代码自动插入 retain 和 release 以及 autorelease等代码,不需要我们手动管理
15.weak指针的实现原理
- Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
- runtime对注册的类, 会进行布局,对于weak对象会放入一个hash表中。 用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc,假如weak指向的对象内存地址是a,那么就会以a为键, 在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil。
16.方法里有局部对象,出了方法后会立即释放吗
- 如果是普通的 局部对象 会立即释放
- 如果是放在了 autoreleasePool 自动释放池,在 runloop 迭代结束的时候释放
17.MRC情况下怎么做单例模式
创建单例设计模式的基本步骤 : ·
- 声明一个单件对象的静态实例,并初始化为nil。
- 创建一个类的类工厂方法,当且仅当这个类的实例为nil时生成一个该类 的实例
- 实现NScopying协议, 覆盖allocWithZone:方法,确保用户在直接分配和 初始化对象时,不会产 生另一个对象。
- 覆盖release、autorelease、retain、retainCount方法, 以此确保单例的 状态。
- 在多线程的环境中,注意使用@synchronized关键字或GCD,确保静态实 例被正确的创建和初始化。
18.非OC对象如何管理内存?
- 非OC对象,其需要手动执行释放操作例:CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free。
19. CADisplayLink、NSTimer会出现的问题,以及解决办法?
问题:
- CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
- CADisplayLink、NSTimer都是基于 runloop 实现的.runloop 会对 CADisplayLink、NSTimer进行强引用, CADisplayLink、NSTimer又 会对 target 进行引用,造成循环引用
- 解决方案1.使用block
// 内部使用 WeakSelf,并在视图消失前,关闭定时器
__weak __typeof(self)weakSelf = self;
NSTimer * timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
self.timer= timer;
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
- 解决方案2.使用代理对象(NSProxy)
.h
// 解决循环引用问题
@interface MyProxy : NSProxy
- (instancetype)initWithObjc:(id)objc;
+ (instancetype)proxyWithObjc:(id)objc;
.m
@interface MyProxy()
@property(nonatomic,weak) id objc;
@end
@implementation MyProxy
- (instancetype)initWithObjc:(id)objc{
self.objc = objc;
return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] initWithObjc:objc];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [self.objc methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
if ([self.objc respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:self.objc];
}
}
使用方法:
NSTimer * timer = [NSTimer timerWithTimeInterval:1
target:[TimerProxy proxyWithTarget:self]
selector:@selector(test1)
userInfo:nil
repeats:YES];
self.timer = timer;
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
20.什么是Tagged Pointer?
- 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
- 在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
- 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
- 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
21.copy和mutableCopy区别
image.png22. 内存泄漏可能会出现的几种原因?
- 第一种可能:第三方框架不当使用;
- 第二种可能:block循环引用;
- 第三种可能:delegate循环引用;
- 第四种可能:NSTimer循环引用
- 第五种可能:非OC对象内存处理
- 第六种可能:地图类处理
- 第七种可能:大次数循环内存暴涨
23. ARC下什么样的对象由 Autoreleasepool 管理
- 当使用alloc/new/copy/mutableCopy开始的方法进行初始化时,会生成并持有对象(也就是不需要pool管理,系统会自动的帮他在合适位置release),不需要pool进行管理
- 一般类方法创建的对象需要使用Autoreleasepool进管理
24. 如何实现AutoreleasePool?
- AutoreleasePool(自动释放池)其实并没有自身的结构,他是基于多个AutoreleasePoolPage(一个C++类)以双向链表组合起来的结构; 可以通过 push操作添加对象,pod 操作弹出对象,以及通过 release 操作释放对象;
25. AutoreleasePoolPage的结构?以及如何 push 和 pod 的?
image.png- 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
- 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
- id *next指向了下一个能存放autorelease对象地址的区域
26.Autoreleasepool 与 Runloop 的关系
- 主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理
iOS在主线程的Runloop中注册了2个Observer
- 第1个Observer监听了
kCFRunLoopEntry
事件,会调用objc_autoreleasePoolPush()
- 第2个Observer 监听了
kCFRunLoopBeforeWaiting
事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit
事件,会调用objc_autoreleasePoolPop()
27.子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?
- 在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用 page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!StackOverFlow 的作者也说道,这个是 OS X 10.9+和 iOS 7+ 才加入的特性。
28.以下代码会有什么问题吗?
Person *p = [[[[Person alloc]init] autorelease] autorelease];
- 以上代码会导致程序崩溃,连续调用2次 autorelease,对象被加入自动释放吃2次, 在释放时候,此一次释放对象就已经销毁了,第二次再释放就会崩溃;
29.以下代码运行会有是问题?
for (int i= 0; i< 1000000; i++) {
NSString * str = [NSString stringWithFormat:@"abcdefghijklmn1234566!@#$"];
}
for (int i= 0; i< 1000000; i++) {
NSString * str = [NSString stringWithFormat:@"abc!"];
}
- 第一个内存会暴涨,str对象会不停的创建,由于是静态方法,会加入到自动释放池,会延迟释放,造成内存暴涨,使用@autoreleasepool 解决
- 第二个内存固定,会使用 Tagged Pointer 将值存在str 变量地址中, 存在栈空间地址中, 出了作用域会自动销毁