iOS面试题整理iOS面试iOS知识点面试

2020iOS面试题精选

2020-08-28  本文已影响0人  Mr_MayBee

一、手机百度(搜索业务)

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.响应者链

  1. Initial view 尝试着去处理事件或者消息。如果不能处理事件,它就递交事件给superview,因为这个initial view并不是视图控制器层级中得顶级view.
  2. 这个superview尝试去处理该事件,如果superview不能处理该事件,它就递交事件给它的父view,因为它也不是view层级的顶级view。
  3. 视图控制器的顶级view尝试着去处理该事件,如果连顶级view都不能处理该事件,它就递交事件给它的controller。
  4. 这个viewcontroller尝试着去处理该事件,并且如果它不能处理该事件,它就会递交事件给window。
  5. 如果window不能处理该事件,它就递交事件给singlegon app object(既UIApplication)
  6. 如果连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

image

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的区别

image
image

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.跨平台技术的了解

算法:https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/solution/mian-shi-ti-67-ba-zi-fu-chuan-zhuan-huan-cheng-z-4/

六、知乎
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在屏幕中显示的整个过程,那些步骤可以放在子线程,那些要在主线程执行

Pasted Graphic.png

1. 百度

2. 腾讯

3. 富途

4. 有赞

5. 方直

6. TCL

7. 编程猫

8. 抖音

9. 随手

上一篇下一篇

猜你喜欢

热点阅读