2020面试题参考
1、说说runtime?
一、基本概念
- RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
- 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
- OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
- 只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
二、怎么实现的
- OC代码调用一个方法
1 | [self login] |
- 在编译时RunTime会将上述代码转化成[发送消息]
1 | objc_msgSend(self,@selector(login)); |
三、有什么作用
- 动态的添加对象的成员变量和方法
- 动态交换两个方法的实现
- 实现分类也可以添加属性
- 实现NSCoding的自动归档和解档
- 实现字典转模型的自动转换
2、SDWebImage实现原理是什么? 它是如何解决tableView的复用时出现图片错乱问题的呢?
一、SDWebImage实现原理是什么:
- 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用
- 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中.
- 从网络上获取,使用,缓存到内存,缓存到沙盒
简单来说就是 SDWebImage获取图片是 先从缓存中获取,如果获取不到的话,再去从网络上获取,网络上获取之后就会存放在内存以及沙盒中,之后再去此图片时就可以直接在内存中以及沙盒中获取
二、解决TableView服用时图片错乱出错问题:
每次都会调UIImageView+WebCache文件中的 [self sd_cancelCurrentImageLoad];
3、描述下SDWebImage里面给UIImageView加载图片的逻辑?
SDWebImage中为UIImageView+WebCache.h,这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage;会在真实图片出现前先显示占位图片,当真是图片被加载出来后再替换上占位图片。
加载图片的大致过程如下:
1、首先会在SDWebImageCache中寻找图片是否有对应的缓存,它会以url作为数据的索引仙子阿内存中寻找是否有响应的缓存
2、如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据,如果找到了,就会把磁盘中的数据加载到内存中,并将图片显示出来
3、如果内存和磁盘中都没有找到,那么就会向远程服务器发起请求,开始下载图片
4、下载后的图片会加入到缓存,并写入磁盘
5、整个获取图片的过程都在子线程中执行,获取图片后会到主线程将图片显示出来
SDWebImage原理:
调用类别的方法:
- 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用
- 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中.
- 从网络上获取,使用,缓存到内存,缓存到沙盒
4、讲讲iOS事件响应链的原理?
1、响应链通常是由视图(UIView)构成的
2、一个视图的下一个响应者是它视图控制器(UIViewController),然后转给它的父视图(Super View)
3、视图控制器的下一个响应者是为其管理的视图的父视图
4、单例窗口(UIWindow)的内容视图将指向窗口本身作为它的下一个响应者(需要指出的是,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,因此整个响应者链要简单点)
5、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环
5、说说多线程?
一、多线程简述
线程是程序执行流的最小单元,一个线程包括:独有ID,程序计数器,寄存器集合,堆栈。同一进程可以有多个线程,它们共享进程的全局变量和堆数据。
这里的PC指向的是当前的指令地址,通过PC的更新来运行我们的程序,一个线程同一时刻只能执行一条指令。当然我们知道线程和进程都是虚拟概念,实际上PC是CPU核心的寄存器,它是实际存在的,所以也可以说一个CPU核心同一时刻只能执行一线程。
不管多处理器设备还是多核设备,开发者往往只需要关心CPU的核心数量,而不需要关心它们的物理构成。CPU核心数量是有限的,也就是说一个设备并发执行的线程数量有限的,当线程数量超过CPU核心数量时,一个CPU核心往往就要处理多个线程,这个行为叫做线程调度。
线程调度简单来说:一个CPU核心轮流让各个线程分别执行一段时间。当然这中间还包含着复杂的逻辑。
二、多线程的优化思路
1、减少队列切换
2、控制线程数量
3、线程优先级权衡
4、主线程任务优化
主线程任务的管理:内存复用、懒加载任务、任务拆分派对执行、主线程空闲时执行任务
三、关于“锁”
多线程会带来线程安全问题。当原子操作不能满足业务时,往往需要使用各种“锁”来保证内存的读写安全。
常用的锁:互斥锁、读写锁、空转锁、递归锁、同步锁,通常情况下,iOS开发中互斥锁
1 |
|
3、最小化临界区:将临界区尽量缩小,不会出现线程安全问题的代码就不要用锁来保护了,这样才能提高并发时锁的性能。
4、时刻注意不可重入方法的安全
5、编译器的过度优化:合理使用锁的地方仍然线程不安全,而volatile关键字就可以解决这类问题。
6、CPU 乱序执行
6、为什么刷新UI要在主线程操作?
第一,为了安全以及效率
因为UIKit框架本身就不是线程安全的,如果多个线程进行UI操作,有可能出现资源抢夺,导致问题
第二,为了用户体验
- iOS中只有主线程才能立即刷新UI。在子线程中是不能够更新UI,我们看到的子线程能够更新UI的原因是,等到子线程执行完毕,自动进入了主线程去执行子线程中更新UI的代码。由于子线程执行时间非常短暂,让我们误以为子线程可以更新UI。如果子线程一直在运行,则无法更新UI,因为没有办法进入主线程。
7、数据库操作FMDB有遇到什么问题吗?
8、用定时器有遇到什么问题吗?
9、说说用的架构?
架构模式:MVC、MVVM、(MVP)反正万变不离MVC
MCV:Model、View、Controller
View – Controller – Model
MVVM:Model、(View、ViewController)、ViewModel,基于 MVC 的,将网络请求,数据存储从ViewController剥离,写在ViewModel
(View、ViewController)– ViewModel – Model
选用 MVVM 的架构模式来重构我们的APP
10、UIScrollView大概是如何实现的,它是如何区分是点击还是滑动手势?
UIScrollView在滚动过程中,其实是在修改原点坐标。当手指触摸后,scrollView会暂时拦截触摸事件,使用一个计时器。假如当计时器到点时没有发生手指移动,那么scrollView则会发送tracking events到被点击的subView。假如计时器到点前发生移动事件,那么scrollView取消tracking自己发生滚动。假如计时器到点前未发生移动,到点后移动的话,则将一开始的触摸事件取消,也不会发生滑动。
11、网络层有封装吗?基于什么封装?封装了什么东西?
当然有封装,基于AFNetworking 3.0
12、PerformSelector和runloop的关系
当调用NSObject的 performSelecter:afterDelay: 后,实际上其内部会创建一个Timer并添加到当前线程的RunLoop。所以当前线程没有RunLoop,则这个方法会失效;
当调用 performSelector:onThread: 时,也会创建一个Timer加入到对应线程,同样,如果对应线程没有 RunLoop 该方法也会失效;
其他的performSelector系列方法是类似的,所以总结 PerformSelector 是依赖于RunLoop
13、KVO实现原理
KVO是一个键值观察者模式,基于runtime机制实现的,当监听某个对象的某个属性时,KVO会创建这个对象的子类,并重写我们监听的属性的set方法。KVO是建立在KVC基础上的,可以看到,如果不通过KVC改变属性,那么set方法就不会执行,那么KVO也就没办法监听属性的变化了。
14、哪些情况下使用kvo会崩溃,怎么防护崩溃
1、observer忘记写监听回调方法observerValueForKeyPath
2、add和remover次数不一致
3、被观察者提前被释放,被观察者在 dealloc 时仍然注册着 KVO,导致崩溃。
4、添加或者移除时 keypath == nil,导致崩溃。
防护:不要忘记写监听回调方法;add与remove次数一致;记得remove
15、block是类吗,有哪些类型
block是类,类型有:globalBlock、stackBlock、mallocBlock
16、一个int变量被__block修饰与否的区别?
没有修饰,被block捕获,是值拷贝
使用__block修饰,会生成一个结构体,复制int的引用地址,达到修改数据
17、block在修改NSMutableArray,需不需要添加__block
不需要
18、block解决循环引用时为什么要用strong、weak修饰
__weak是为了防止循环引用
__strong是为了防止block持有的对象提前释放
19、iOS开发中有多少类型的线程?分别对比
NSThread、NSOperation、GCD
- NSThread比其他两种较为轻量级,使用简单。但是,它需要自己管理线程生命周期,线程同步,加锁,睡眠以及唤醒等等。线程同步对数据的加锁会有一定的系统开销
- NSOperation不需要关心线程管理,数据同步,可以把精力放在自己需要执行的操作上,NSOperation是面向对象
- GCD是苹果开发的一个多核编程方案,iOS4+才能使用,替代NSThread、NSOperation的高效强大技术
20、GCD有哪些队列,默认提供哪些队列
串行队列、并发队列、主队列、全局队列
21、iOS GCD 常用API
1、Dispatch Queue
2、1
2
3
4
3、Main Dispatch Queue/Global Dispatch Queue
4、```diapatch_set_target_queue
5、1
2
3
4
6、Dispatch Group
7、```dispatch_barrier_async
8、1
2
9、```dispatch_once
10、1
2
11、```dispatch_suspend/dispatch_resume
12、Dispatch Semaphore
22、iOS GCD 线程同步方法
一、dispatch_group 线程组
二、dispatch_barrier 栅栏块
三、dispatch_semaphore 信号量
23、dispatch_once实现原理
dispatch_once主要是根据onceToken的值来决定怎么去执行代码。
1.当onceToken= 0时,线程执行dispatch_once的block中代码
2.当onceToken= -1时,线程跳过dispatch_once的block中代码不执行
3.当onceToken为其他值时,线程被阻塞,等待onceToken值改变
24、IOS开发 GCD产生死锁的总结
了解死锁之前首先要明白几个常识:
- 1、同步与异步的区别在于能否“开启”新的线程
- 2、串行与并发的区别在于能否“同时”执行任务
- 3、把任务添加到队列[queue]中,按照“first in first out”原则执行任务
- 4、串行队列中执行任务特点:需要一个任务执行完毕之后,才能执行下一个任务,并发则不用。
- 5、同步执行任务是立即执行,异步则不是。
总结:
1、“同步”(sync)且在“当前队列【主线程】”(global)中执行任务,必定死锁
2、“同步”且在“同一个串行队列”中执行任务,必定死锁
25、Block为什么加了__block就可以改变变量
Block不允许修改外部变量的值,外部变量指的是栈中指针的内存地址。__block所起的作用就是只要观察到变量被block所持有,就可以将“外部变量”在栈中的内存地址放到堆中,进而在block内部也可以修改外部变量的值。
26、如何优化UITableView
- 正确使用UITableView的重用机制
- 以前计算好cell的高度与布局
- 避免阻塞主线程
- 按需加载
- 尽可能重用开销大的对象
- 尽可能减少计算复杂度
- 不要动态add或remove子控件
还是不行的话 使用调试工具(Instruments)、异步绘制
27、如何使用队列来避免资源抢夺?
当我们使用多线程访问用一个数据时,有可能造成数据不准确性,这时我们需要使用线程锁绑定,也可以用串行队列来完成任务。
28、id声明对象的特性?
id声明的对象具有运行时特性,可以指向任意类型的OC对象
29、Category、Extension以及继承的区别
Category与Extension,Category是只能添加方法,而Extension不仅能添加方法还能添加属性以及成员变量;继承可以增加、修改、删除方法,并且可以增加属性。
30、KVC底层实现
当一个对象调用setValue方法时,方法内部操作:
- 检查是否存在相应的key的set方法,如存在则调用set方法
- 如set方法不存在的话,就查找与key相同的名称并且带下划线的成员变量,如果有则直接给成员变量属性赋值
- 如果没有找到key,就查找相同名称的属性key,如有则直接赋值
- 如果还没有,则调用valueForUndefinedKey:和setVale:forUndefinedKey:方法,这些方法实则就是抛出异常,则根据需要重写
31、如何访问并修改一个类的私有属性
1、通过KVC获取
2、通过Runtime访问修改私有属性
32、iOS沙盒机制原理
沙盒机制就是iOS应用程序只能在该程序创建的文件系统中读取文件,不可以去其他地方访问。
沙盒有四个文件夹:Documents、Library、tmp、app包
33、如何重构代码
1、先指定一套代码规范
2、决定使用哪种框架适合现在业务
3、整理一下资源文件、文件目录结构
4、升级各种框架
5、性能优化
6、安全防护
34、单例的优缺点
优点:
1、一个类只被实例化一次
2、节省系统资源
3、允许可变数目的实例
缺点:
1、一个类只有一个对象,可能造成责任过重
2、单例模式中没有抽象层,因此单例扩展比较困难
3、滥用单例会导致一些问题,如为了节省资源数据将数据库连接池设计为单例类,可能导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而回收,这将导致对象状态的丢失
35、如何防止反编译
1、本地数据加密
2、URL编码加密
3、网络传输数据加密
4、方法体,方法名高级混淆
5、程序结构混排加密
36、OC如何对内存管理
- ARC:自动内存计数器,由Xcode自动在APP编译阶段,在代码中添加内存管理代码
- MRC:手动内存计数器,遵循内存谁申请谁持有谁释放原则
- ReleasePool,内存池把需要释放的内存统一放在一个池中,当池子被排干(drain),池子中所有的内存空间也自动被释放,内存池操作分自动和手动,自动释放runloop机制影响
37、Block与delegate的区别
Block是代码块比delegate的成本高,因为block需要将数据从栈内复制到堆内,直到使用完Block置为nil才结束;delegate的话就是保存对象指针,直接回调就ok。
Block轻量级,delegate重量级
38、网络模型
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理层
⽅法的本质,sel是什么?IMP是什么?两者之间的关系⼜是什么
方法本质,消息 objc_msgSend。
SEL:类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。
IMP:一个函数指针,保存了方法的地址
查找流程:快速编译 慢速递归 动态解析 快速转发 慢速转发
两者关系:通过方法编号SEL来查找对应的IMP,并实现函数执行方法
字典的实现原理
1、字典(NSDictionart)是使用hash表来实现key和value的存储与映射的,hash函数设计的好坏影响着数据的查找访问率。
2、Object-C中的字典NSDictionary底层是一个哈希表。
setNeedsLayout与layoutIfNeeded的区别
一、layoutSubviews
不能直接调用这个方法。如果需要强制刷新布局,调用setNeedsLayout方法,如果想马上刷新界面,调用layoutIfNeeded
二、setNeedsLayout跟layoutIfNeded
setNeedsLayout调整视图的子视图布局时,是在应用程序的主线程用此方法的
如果要立即刷新,要先调用[view setNeedsLayout],把标记设为需要布局,然后马上调用[view layoutIfNeeded],实现布局
在视图第一次显示之前,标记肯定是“需要刷新”的,所以直接调用[view layoutIfNeeded]就会进行立即更新
property的作用是什么,有哪些关键词,分别是什么含义?
property的作用自动生成私有属性 自动生成私有属性getter和setter方法的声明 自动生成私有属性的getter和setter方法的实现
关键词有:setter、getter、atomic、nonatomic、assign、retain、readwrite、readonly
atomic:安全性高但是效率相对较低
nonatomic:安全性低但是效率相对较高
assign:生成setter方法的实现就是直接赋值,属性的类型是非OC对象的时候 就使用assign
retain:生成setter方法的实现就是标准的MRC内存管理实现,属性的类型是OC对象的时候 就使用retain
readwrite:可写可读,同时生成属性的getter和setter
readonly:只读属性,只生成属性的getter
TCP和UDP的区别以及应用场景
TCP:面向连接,可靠,有序性,面向字节流
UDP:无连接,不可靠,无序,面向报文
应用场景:TCP效率要求低,但准确性相对高的场景;UDP效率要求高,但准确性相对低的场景。
iOS中属性weak底层实现原理和使用场景
1、通过weak编译解析,可以看出来weak通过runtime初始化的并维护的;
2、weak和strong都是Object-C的修饰词,而strong是通过runtime维护的一个自动计数表结构。
综上:weak是有Runtime维护的weak表。
使用场景:防止循环引用的时候使用weak
CADisplayLink的基础 以及CADisplayLink与NSTimer的比较
CADisplayLink:1、在屏幕刷新的时候使用;2.延迟;3、使用场景:CADisplayLink适合界面的不停重新绘制。
CADisplayLink与NSTimer之间的差异:1、精确度,NSTimer较低;使用场合:CADisplayLink使用场景相对转移,适合用于UI不停重绘。NSTimer使用场景较广泛。