2020iOS面试题精选
一、手机百度(搜索业务)
1.技术亮点、难点。
2.web加载渲染过程
3.组件化,路由和taget-action两种方案的优劣
4.算法
a>判断链表是否有环,一亿个数
快慢指针,直到两个指针相遇或者到达尾部
b>如何用100M的内存筛选出最小的100个数。
https://www.jianshu.com/p/119c1ff5ea69
5.runloop线程保活(具体代码是什么样子的)
https://www.jianshu.com/p/4d5b6fc33519
6.runloop监听卡顿(具体代码是什么样子的)
https://www.jianshu.com/p/ef2599f7251f
7.网路层的优化
要点 :dns缓存 弱网环境优化 包体积大小
域名合并:淘宝、美团等公司公布的解决方案中都有提到,就是将公司原来的很多域名都合并到较少的几个域名。为什么?因为 HTTP 的通道复用就是基于域名划分的。如果域名只有几个,那么多数请求都可以在长连接通道进行,这样就可以降低延迟、增加成功率
预热,尽早建立长连接。这样其他的业务请求就可以复用长连接通道。加快访问速度。因为每次建立连接都需要经过 DNS 域名解析、TCP 三次握手等漫长步骤。建立长连接的时机可以考虑:冷启动、前后台切换、网络切换等
如果情况允许,可以将网络切换到 HTTP 2.0,解决了 HTTP1.1 的 head of blocking ,降低了网络延迟,提供了更强大的多路复用技术。还加入了流量控制、新的二进制格式、Server Push、请求优先级和依赖等待等特性。
建立多通道。比如携程、艺龙、美团等公司都有自己的 TCP、UDP 通道。具有多域名共用通道。
有些超级大厂还自研了协议。比如 QUIC
加入 CDN 加速,动态静态资源分离
对于类似埋点的业务数据请求,可以合并请求,减小流量。另外结合埋点数据压缩上传
App 网络情况诊断
根据网络情况,动态设置超时时间等
https://zhuanlan.zhihu.com/p/115134324
8.包大小优化
https://blog.csdn.net/xj1009420846/article/details/80313566
9.启动速度优化(runtime和dyld阶段之间还有其他的)减少不必要的framework,因为动态链接比较耗时
check framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查
合并或者删减一些OC类,关于清理项目中没用到的类,使用工具AppCode代码检查功能,查到当前项目中没有用到的类如下:
image
删减一些无用的静态变量
删减没有被调用到或者已经废弃的方法
方法见:http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7
https://developer.Apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html
将不必须在+load方法中做的事情延迟到+initialize中
尽量不要用C++虚函数(创建虚函数表有开销)
main()调用之后的加载时间
在main()被调用之后,App的主要工作就是初始化必要的服务,显示首页内容等。而我们的优化也是围绕如何能够快速展现首页来开展。 App通常在AppDelegate类中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中创建首页需要展示的view,然后在当前runloop的末尾,主动调用CA::Transaction::commit完成视图的渲染。
而视图的渲染主要涉及三个阶段:
准备阶段 这里主要是图片的解码
布局阶段 首页所有UIView的- (void)layoutSubViews()运行
绘制阶段 首页所有UIView的- (void)drawRect:(CGRect)rect运行
再加上启动之后必要服务的启动、必要数据的创建和读取,这些就是我们可以尝试优化的地方
因此,对于main()函数调用之前我们可以优化的点有:
不使用xib,直接视用代码加载首页视图
NSUserDefaults实际上是在Library文件夹下会生产一个plist文件,如果文件太大的话一次能读取到内存中可能很耗时,这个影响需要评估,如果耗时很大的话需要拆分(需考虑老版本覆盖安装兼容问题)
每次用NSLog方式打印会隐式的创建一个Calendar,因此需要删减启动时各业务方打的log,或者仅仅针对内测版输出log
梳理应用启动时发送的所有网络请求,是否可以统一在异步线程请求
https://www.jianshu.com/p/7096478ccbe7
10.离屏渲染
runloop有个60fps回调,绘制内容交给GPU渲染,包括view拼接,纹理的渲染。
CPU计算好显示内容提交到GPU,GPU渲染完成后将渲染结果放入帧缓冲区
1 当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行
2 离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操
3 重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染
4 CoreGraphic通常是线程安全的,所以可以进行异步绘制,显示的时候再放回主线程
11.响应者链
- Initial view 尝试着去处理事件或者消息。如果不能处理事件,它就递交事件给superview,因为这个initial view并不是视图控制器层级中得顶级view.
- 这个superview尝试去处理该事件,如果superview不能处理该事件,它就递交事件给它的父view,因为它也不是view层级的顶级view。
- 视图控制器的顶级view尝试着去处理该事件,如果连顶级view都不能处理该事件,它就递交事件给它的controller。
- 这个viewcontroller尝试着去处理该事件,并且如果它不能处理该事件,它就会递交事件给window。
- 如果window不能处理该事件,它就递交事件给singlegon app object(既UIApplication)
- 如果连application都不能处理该事件,那么毫无疑问该事件将会被丢弃。
响应事件+响应者链条(单向 从子控件到父控件)
UIAppliction --> UIWiondw -->递归找到最适合处理事件的控件-->控件调用touches方法-->判断是否实现touches方法-->没有实现默认会将事件传递给上一个响应者-->找到上一个响应者
用户点击屏幕后产生的一个触摸事件,经过一些列的传递过程后,会找到最合适的视图控件来处理这个事件
整个响应顺序是从上到下再从下到上; 响应者链的响应事件是从下到上
如何找到合适控件处理事件 触摸事件是从父控件传递到子控件
1 自己能否接收触摸事件
2 触摸点是否在自己身上
3 从后往前遍历子控件 重复前面2个步骤
4 没有合适的子控件 就自己做处理
二、快手
1.算法
a> 两个数n、m 如果是n= 2 m=5,用递归实现2 3 4 5相加等于14;
public static int sum(int n1, int n2) {
if(n1 == n2) {
return n1;
}
if(n1 > n2) {
int temp = n1;
n1 = n2;
n2 = temp;
}
return sum(n1, n2-1) + n2;
}
2.weak和assign的区别
一、区别
1.修饰变量类型的区别
weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。
assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是"unsafe_”。
2.是否产生野指针的区别
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
二、相似
都可以修饰对象类型,但是assign修饰对象会存在问题。
三、总结
assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。
weak 适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
3.weak原理(很细致,具体到如何查找的)
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating。
https://blog.csdn.net/future_one/article/details/81606895
4.autoreleasePool 的结构和自动释放池中的对象存储过程,autoreleasePool的结构。(比较细致的问了一遍过程)
关键字:结构体 parent和child 双向链表
https://www.jianshu.com/p/58dab9c28a12
5.objc_msgSend()经历的过程,具体到cache_t结构和具体hashmap的查找方法。动态解析和消息转发要说具体的方法名称。
objc_msgSend汇编部分仅仅完成很少的缓存查找功能,如果找不到就会调用C方法去对象的方法二维数组中找,找不到再查父类的缓存(这也是汇编实现的)和父类的方法数组,一直找到根类,如果此过程中找到对应的方法则调用并添加缓存,如果没有找到,则表明该继承体系都没有直接实现该方法,这时runtime会调用对象的方法决议去尝试解决。如果不行则由CoreFoundation框架提供的forwarding来转发到其他对象处理,若还不能处理则抛出异常。
https://www.jianshu.com/p/75a4737741fd
6.block的捕获机制,block类型的区分,__block做了什么,__block修饰对象类型和基本数据类型的区别。
https://blog.csdn.net/DreamcoffeeZS/article/details/102257488
https://blog.csdn.net/DreamcoffeeZS/article/details/102475351
7.load()和initialize()区别
<meta charset="utf-8">
调用方式
1、load是根据函数地址直接调用
2、initialize是通过objc_msgSend调用
调用时刻
1、load是runtime加载类、分类的时候调用(只会调用一次)
2、initialize是类第一次接收到消息的时候调用, 每一个类只会initialize一次(如果子类没有实现initialize方法, 会调用父类的initialize方法, 所以父类的initialize方法可能会调用多次)
load和initializee的调用顺序
1、load:
先调用类的load, 在调用分类的load
先编译的类, 优先调用load, 调用子类的load之前, 会先调用父类的load
先编译的分类, 优先调用load
2、initialize
先初始化分类, 后初始化子类
通过消息机制调用, 当子类没有initialize方法时, 会调用父类的initialize方法, 所以父类的initialize方法会调用多次
8.图层方法两倍形变后,frame和bouns的变化,相对位置,绝对位置。
三、快手二面
1.算法
a>爬楼梯,动态规划,是否可以优化空间复杂度
b>一面时面试题的变种
2.__strong和__weak的区别
3.strong和copy的区别
4.看代码(题目较多)
5.项目中重要的技术点
6.实现线程同步方案的几种方式的优缺点
四、探探/网易有道
1、算法
a> LRU(最近最少使用次数、最近最晚使用)
https://blog.csdn.net/elricboa/article/details/78847305
b>哈希冲突
开放定址法 拉链法
https://blog.csdn.net/xtzmm1215/article/details/47177701
c>哈希表以对象为键,怎么处理
2.A,B,C三个线程 打印array中元素,【1,2,3,4……100】,A线程打印1,B打2,C打3,A打4,依次打印。
https://www.jianshu.com/p/40078ed436b4
3.kvo监听一个weak修饰的属性,当对象释放的时候,kvo回调方法会被释放吗
kvo作为一个中间对象,在当前控制器销毁时任然会存在,所以在销毁时应该移除当前观察释放kvo对象
4.100*100像素的图片在内存中的大小,
一个像素是RGB + alpha 一个是16位,所以是100 * 100 * 4 *16 位
5.看代码的题目比较多
五、美团
1.block对mutablearray 修改需要用__block吗?对block的捕获是深拷贝还是浅拷贝?
浅拷贝,可以在block中修改数组中的元素
2.GCD和operation的区别
imageimage
3.GCD是如何实现线程调度的
https://blog.csdn.net/zhangshichi/article/details/51161245
3.1GCD怎么实现线程同步的
组队列(dispatch_group) 阻塞任务(dispatch_barrier)
https://www.jianshu.com/p/81576172ad1f
4.线程同步的方案,
5.锁有哪些,有什么区别
6.读写锁实现的原理
7.内存管理的理解
8.方法查找
9.MVC和MVVM的区别
10.项目架构
11.autoreleasePool
12.autoreleasePool和runloop的关系
13.实际开发中autoreleasePool的作用以及应用
14.kvo底层原理
检查对象的类有没有相应的 setter 方法。如果没有抛出异常;
检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;
检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;
添加这个观察者
http://www.cocoachina.com/articles/11321
15.哈希冲突的解决方案
16.循环引用的原因,举例说明
https://www.jianshu.com/p/3ffa8bc19cbe
17.线程导致死锁的原因,举例说明
互斥条件 请求和保持条件 不剥夺条件 环路等待条件
18.weak和strong的区别,和assign的区别
19.llvm的编译流程
https://www.jianshu.com/p/333cf1c02a0e
20.对swift和oc的区别
21.跨平台技术的了解
六、知乎
1.组件化拆分的过程问答的很细致,以及拆完组件化是否还有可以优化的
2.包体积优化
3.启动优化
4.dyld阶段的细节
5.autoreleasepool和线程有什么关系
6.数组中有1万个数字,如果删除某一个元素,得到一个新的数组,怎么判断出删除的是几???
7.手触摸到屏幕,整个事件的响应全过程。
8.用协议实现一个一对多的通知效果
9.判断链表是否有环,且环是多少个节点
10.用栈实现一个数组的增删改查
11.设计上报日志的库
12.抽象工厂和工厂有什么区别
七、腾讯(个人觉得面试官实力有点差)
1.引用计数的理解?系统为什么这么设计
2.runloop和autoreleasepool的关系,子线程创建的时候需要些一个autoreleasepool吗,为什么?
3.webView和WKWebView的区别
4.实现多线程的方式,各有什么优缺点
多线程有多种实现方式,常见的有以下三种:
1、继承Thread类,重写run()方法。
1) 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
2)创建Thread子类的实例即创建了线程对象。
3)调用线程对象的start()方法启动线程。
2、实现Runnable接口,重写run()方法。
1)定义Runnable接口的实现类,并重写该方法的run()方法,该run()方法同样是该线程的执行体。
2)创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3)调用线程对象的start()方法启动线程。
3、通过实现Callable接口和使用FutureTask包装器来实现线程。
1)创建Callable接口的实现类,并实现call()方法,该call()方法的方法体同样是该线程的执行体。
2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
三种实现方式的优缺点对比:
1、实现Runnable和Callable接口方式:
优点:
1)线程类只是实现了Runnable接口(JDK1.0开始)或Callable接口(JDK1.5开始),还可以继承其他类。
2)多线程可以共享同一个target对象,非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
3)实现Callable接口创建多线程最大的好处是可以有返回值。
缺点:
编程稍显复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2、使用继承Thread类方式:
优点:
编写简单,如果要访问当前线程无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
缺点:
线程类已经继承了Thread类,不能再继承其他类(java的单继承性),因此该方式不够灵活。
补充说明:
1)Callable规定重写call()方法;Runnable重写run()方法。
2)Callable的任务执行结束后可有返回值;Runnable的任务是不能有返回值的。
3)call()方法可以抛出异常;run()方法不可以。
4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检查计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,可以获取执行结果。
5.iOS如何实现多继承,代码书写一下。
代理 消息转发
动态方法解析:向当前类发送resolveInstanceMethod: 信号,检查是否动态向该类添加了方法
快速消息转发:检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法,若该方法返回nil或者非self,则向该返回对象重新发送消息
标准消息转发:runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出.
6.创建子线程需要创建一个autoreleasepool吗?
https://blog.csdn.net/qq_22389025/article/details/85162240?utm_medium=distribute.pc_relevant.none-task-blog-title-1&spm=1001.2101.3001.4242
不需要,但是他说如果不出创建autoreleasepool对象,如果有autorelease修饰的对象会有警告,简直是胡扯。
7.为什么不能在子线程刷新UI
https://www.jianshu.com/p/5849eb69ec82
8.一个imageView在屏幕中显示的整个过程,那些步骤可以放在子线程,那些要在主线程执行
1. 百度
- 一面
- MVC、MVP、MVVM的区别,使用场景
- NSLayout、Masonry、SpanKit的区别,使用场景
- ARC内存管理机制
- 对Runtime的了解
- 对Runloop的了解
- 项目中遇到的困难
- 性能优化
- App体积优化
- 启动时间优化
- 离职原因
- 你开发的SDK的使用量
- 用Python做过什么
- 对开发的这个App有没有自己的看法
- 最近看了什么书
- 发展方向(职业规划)
- 对百度地图了解吗
- 对加班的看法
- 你想问的问题
- 有其他公司的offer吗
- 想问的问题
- 二面
- 谈谈你做的自动化测试
- GCD的几个题目做一下
- 子线程中创建NSTimer会执行吗
- 在dealloc中释放NSTimer会怎样
- NSNoticationCenter不释放监听者着会怎样
- 自动释放池什么时候释放
- Runloop何时退出
- 互斥锁、自旋锁
- NSLock、递归所、读写锁的区别和使用场景
- 看过哪些源码
- NSNoticationCenter的实现原理,如何自定义
- KVO原理
- 手写:字符串中寻找是否出现了重复的字符
- 两层循环
- hash
- 设计hash表
- 想问的问题
- 三面
- 项目架构
- 职业规划
- 遇到的困难
- 对项目的贡献
- 离职原因
- 如何看待加班
- 和同事有矛盾如何解决
- 你的优点
- 你的缺点
- 想问的问题
2. 腾讯
- 一面
- 谈谈你做的自动化测试
- Block中捕获全局、静态、实例、局部变量是怎样实现的
https://www.jianshu.com/p/ee9756f3d5f6
自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量。 - ARC内存管理机制
- Runloop
- Runtime
- 性能优化
- App体积优化
- 启动时间优化
- 动/静态库分别在哪里占用了时间?
- 项目中遇到的困难
- 字符串转数字
- 你想问的问题
3. 富途
- 一面
- ARC内存管理机制
- Runloop
- Runtime
- 性能优化
- App体积优化
- 启动时间优化
- KVO原理
- struct和class的区别
- swift协议
- 手写快排
- 手写,数组[1,0,3,0,4,0,0,-2],将0放在数组最前面且不影响非0值的顺序
- 手写翻转二叉树
- 绳子粗细不均匀,但烧完一定需要60分钟,如何烧出45分钟。
- 想问的问题
- 二面
- 启动时间优化
- 结构体 { char int int } 占用多少字节,内存对齐是什么
- int a = 0, 两个线程中各加十次,a=多少,为什么,a的范围是多少?
- 员工表,18年3月-18年8月工资少发了,现在需要对其补发,手写SQL
- 25个人有5个跑道,求出最快的三个人,至少需要比赛多少次
- 有2个玻璃珠和100层楼,玻璃珠摔坏了则无法二次使用,求玻璃球能承受的最大高度,至多需要多少次测试。
- 1000瓶水,1瓶有毒,老鼠喝水后3天死亡,给3天时间,求至少需要多少只老鼠。
- 手写二叉搜索树插入函数
- 求无序数组中最大的K个数
- 想问的问题
4. 有赞
- 一面
- Runloop
- Runtime
- 消息转发机制
- 事件传递机制
- 性能优化
- App体积优化
- 启动时间优化
- KVO原理
- 组件化
- 职业规划
- 想问的问题
- 二面
- 你做了什么功能,画出并解释流程图和项目结构
- 你的职业规划
- 想问的问题
- 三面
- 项目架构
- 你的职业规划
- 想问的问题
5. 方直
- 一面
- Runtime
- Runloop
- 性能优化
- App体积优化
- 启动时间优化
- 启动流程
- KVO原理
- 事件传递机制
- 消息转发机制
- 自动释放池内部实现
- Storyboard、classify
- 想问的问题
- 二面
- 性能优化
- 项目架构
- 你能为公司带来什么
- 你的价值
- 你的职业规划
- 想问的问题
6. TCL
- 一面
- Runloop
- Runtime
- OC底层结构
- 消息转发机制
- 事件传递机制
- 性能优化
- App体积优化
- 启动时间优化
- 启动流程
- KVO原理
- 组件化
- load和initlize的区别
- 两个相同的油漆桶,一个红色一个蓝色,一个勺子,使用勺子从红桶挖一勺到蓝桶并搅拌均匀,此时从蓝桶挖一勺到红桶搅拌均匀,问红桶中的红蓝比和蓝桶中的蓝红比是怎样的
- 手写归并排序
- 想问的问题
- 二面
- 性能优化
- App体积优化
- 启动时间优化
- 组件化
- 项目的产品角度
- 项目架构
- 想问的问题
7. 编程猫
- 一面
- Runloop
- 性能优化
- App体积优化
- 启动时间优化
- 启动流程
- KVO原理
- 暗黑模式适配
- 了解哪些设计模式
- 一个按钮会触发网络加载然后更新列表,用MVVM说思路
- RAC的冷信号和热信号区别
- 说说自动化测试
- Bugly解决不了的异常,怎么处理的
- atomic, weak
- 链表和数组的区别,队列和栈的区别
- SwiftUI
- 想问的问题
8. 抖音
- 一面
- 说一个你做的App有什么功能
- 说一说用到了AVFoundation的哪些功能
- AVFoundation相机代理的视频流返回的图片是什么格式,如何显示在屏幕上
- KVO原理
- struct和class的区别,如果for循环1000次struct会怎样
- unowned和weak的区别
- NSTimer准吗,不准的话会延时调用吗,如何解决
- AutoRelease对象什么时候释放
- Runloop什么时候释放
- Runloop干了什么
- 自动释放对象在Runloop的哪个阶段释放
- 野指针如何产生的
- 使用Block时,为什么会使用__strong或__weak
- 离屏渲染是什么,如何产生的,如何解决
- 屏幕上的内容是如何显示的,卡顿是如何产生的,如果解决
- 以对角线顺序打印二维数组
- 想问的问题
9. 随手
- 一面
- Runloop
- Runtime
- OC底层结构
- 消息转发机制
- 事件传递机制
- 性能优化
- App体积优化
- 启动时间优化
- 启动流程
- KVO原理
- 英文笔试题:给NSString添加一个分类,实现将其转换为NSNumber
- 英文笔试题:如何产生循环引用
- 英文笔试题:设计低耦合的<花开蜂猜谜,花闭蜂回家>
- 英文笔试题:寻找数组中出现次数最多的数值的和
- 想问的问题
- 二面
- 消息转发机制
- 事件传递机制
- 性能优化
- App体积优化
- 启动时间优化
- 组件化
- 向我解释什么是GCD,假设我不懂iOS。
- 职业规划
- 想问的问题