2020面试题

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开发中互斥锁

pthread_rwlock_t 就能满足大部分需求,且性能不错。```
1
2
3
4
5
6
7
8
9
10
11
12
13

在读取锁失败时,线程可能有两种状态:

* 空转状态:线程执行空任务循环等待,当锁可用时立即获取锁。
* 挂起状态:线程挂起,当锁可用时需要其他线程唤醒。

唤醒线程时比较耗时,线程空转需要消耗CPU资源并且时间越长消耗越多,由此可知空转适合少量任务,挂起适合大量任务。

实际上互斥锁和读写锁都有空转锁的特性,它们在获取锁失败时会先空转一段时间,然后才会挂起,而空转锁也不会永远的空转,在特定的空转时间过后仍然会挂起,所以通常情况下不用刻意使用空转锁。

1、OSSpinLock优先级反转问题

2、```避免死锁:同一线程重复获取锁导致的死锁,这种情况可以使用递归锁来处理,pthread_mutex_t 使用 pthread_mutex_init_recursive() 方法初始化就能拥有递归锁的特性。

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使用场景较广泛。