美团优选面试题及答案

美团优选面试题及答案

美团优选面试题

技术栈是怎么选择的?为什么选择Flutter,没有选择RN

Flutter 与 React Native 的对比分析

1.链表环的入口结点(限制15分钟)

算法1:遍历链表,使用Set去存一下

1
2
3
4
5
6
7
8
9
10
11
function detectCycle( head ) {
const visited = new Set();
while (head != null) {
if (visited.has(head)) {
return head;
}
visited.add(head);
head = head.next;
}
return null;
}

算法2:快慢指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function detectCycle( head ) {
if (!head) return null;
let slow = head, fast = head;
while(fast.next && fast.next.next) {
fast = fast.next.next
slow = slow.next
if (slow == fast) {
var a = head;
while (slow != a) {
a = a.next
slow = slow.next
}
return a;
}
}
return null;
}

2.首页的轮播图,滑动主区域 timer会不会停止?

Timer会停止。因为滑动时Runloop的mode由原来的DefaultMode切换到了UITrackingRunLoopMode。同一时间只能有一种mode。

解决方法其一是将timer加入到NSRunloopCommonModes中。其二是将timer放到另一个线程中,然后开启另一个线程的runloop,这样可以保证与主线程互不干扰,而现在主线程正在处理页面滑动。示例代码如下:

1
2
3
4
5
6
7
// 方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});

3.滑动屏幕,从响应者的角度和手势的角度来分析系统做了什么事情

从手指触摸屏幕,触摸事件的传递大概经历了3个阶段,系统响应阶段–>SpringBoard.app处理阶段–>前台App处理阶段,大致的流程如下图:

图1-事件的生命周期

起始阶段
  • cpu处于睡眠阶段,等待事件发生
  • 手指触摸屏幕
系统响应阶段
  • 屏幕感应到触摸事件,并将感应到的事件传递给IOKit(用来操作硬件和驱动的框架)
  • IOKit.framework封装整个触摸事件为IOHIDEvent对象,直接通过mach port(Mach属于硬件层,仅提供了诸如处理器调度、IPC进程通信等非常少量的基础服务。)转发给SpringBoard.app。
SpringBoard.app处理阶段
  • SpringBoard.app的主线程Runloop收到IOKit.framework转发来的消息苏醒,并触发对应mach port的Source1回调__IOHIDEventSystemClientQueueCallback()。
  • 如果SpringBoard.app监测到有App在前台(记为xxx.app),SpringBoard.app再通过mach port转发给xxx.app,如果SpringBoard.app监测到前台没有App运行,则SpringBoard.app进入App内部响应阶段,触发自身主线程runloop的Source0时间源的回调。

SpringBoard.app是一个系统进程,可以理解为桌面系统,可以统一管理和分发系统接收到的触摸事件。

App内部响应阶段
  • 前台App主线程Runloop收到SpringBoard.app转发来的消息而苏醒,并触发对应mach port的Source1回调__IOHIDEventSystemClientQueueCallback()。
  • Source1回调内部,触发Source0回调__UIApplicationHandleEventQueue()
  • Source0回调内部,封装IOHIDEvent为UIEvent。
  • Source0回调内部,调用UIApplication的sendEvent:方法,将UIEvent传给UIWindow,接下来就是寻找最佳响应者的过程,也就是命中测试hit-testing。
  • 寻找到最佳响应者后,接下来就是事件在响应链中的传递和响应了。需要注意的是,事件除了可以被响应者处理之外,还有可能被手势识别器或者target-action捕捉并处理,这涉及到一个优先级的问题。如果触摸事件在响应链中没有找到能够响应该事件的对象,最终将被释放。
  • 事件被处理或者释放之后,runloop如果没有其他事件进行处理,将会再次进入休眠状态。

寻找事件的最佳响应者(Hit-Testing)

能够响应触摸事件的例如UIView,UIButton,UIViewController,UIApplication,Appdelegate等都继承自UIResponder类,一个页面上通常会有许许多多个这种类型的对象,都可以对点击事件作出响应。为了避免冲突,这就需要有一个先后顺序,也就是响应的优先级。Hit-Testing的目的就是找到具有最高优先级的响应对象。
寻找的具体流程如下:

  1. UIApplication首先将事件队列中的事件取出,传递给窗口对象。如果有多个窗口,则优先询问windows数组的最后一个窗口。
  2. 如果窗口不能响应事件,则将事件传递给倒数第二个窗口,以此类推。如果窗口能够响应事件,则再依次询问该窗口的子视图。
  3. 重复步骤2。
  4. 若视图的所有子视图均不是最佳响应者,则自身就是最合适的响应者。
    另外需要注意的是,一下几种状态的视图无法响应事件:
  • 不允许交互的视图:userInteractionEnabled = NO
  • 隐藏的视图:hidden = YES
  • 透明度alpha<0.01的视图

怎么样验证一下上面所说的Hit-Testing的顺序呢,看一下UIView的API,里面会有一个hitTest:withEvent:方法,这个方法的主要作用就是查询并返回事件在当前视图中的响应者,每个被询问到的视图对象都会调用这个方法来返回当前视图层的响应者。

  • 如果当前视图无法响应事件,则返回nil。
  • 如果当前视图可以响应事件,但子视图不能响应事件,则返回自身作为当前视图的响应者。
  • 如果当前视图可以响应事件,同时有子视图可以响应事件,则返回该子视图作为当前视图的响应者。

参考链接:iOS触摸事件处理

扩展阅读:UIButton在执行动画的时候,点击事件不响应的解决办法

4.网络请求回来后,回到主线程刷新UI,为什么有时候会延迟?

主线程的RunLoop可能当时在处理的任务比较重,需要执行完后再执行刷新UI操作。

5.performSelector了解吗?performSelector:afterDelay:怎么确保执行的线程不被销毁。

[self performSelector:@selector(test) withObject:nil afterDelay:.0]实际在runloop里面,是一个定时器,但是因为在子线程,runloop是默认没有开启的。

performSelector:afterDelay:之后开启runloop,即[[NSRunLoop currentRunLoop] run];

port的生命周期结束,runloop也没了,那怎么保证runloop没销毁?

在子线程调用方法前去开启Runloop。

6.谈谈autorelease和autoreleasepool

autorelease的基本用法
1) 会将对象放到一个自动释放池中
2) 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
3) 会返回对象本身
4) 调用完autorelease方法后,对象的计数器不变

autorelease的具体使用方法

  • 生成并持有NSAutoreleasePool对象
  • 调用已分配对象的autorelease实例方法
  • 销毁NSAutoreleasePool对象

NSAutoreleasePool对象生存周期相当于C语言的局部变量作用域,对所有调用过autorelease的实例方法的对象,在销毁NSAutoreleasePool对象时,都将调用release实例方法,即实例对象调用release方法。

当对象调用了autorelease方法是实现实际大致可以理解为是调用的NSAutoreleasePool调用了类方法addObject

1
2
3
4
- (void)autorelease 
{
[NSAutoreleasePool addObject: self];
}

AutoreleasePool

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件:

  • BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧的池并创建新池;
  • Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。

7.student类在读书方法里加代码,不改变原方法

可以使用 Runtime 或者子类。

iOS系统中有没有用类似的方法实现?

KVO

8.KVO实现原理,KVO打印实例对象的class为什么能够做到还是原本类。

1) 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
2) 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数

1
2
3
4
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

注意事项:需要调用set方法触发KVO

实现了class方法,返回了父类。

9.http get和post区别

Get是安全的、幂等的、可缓存的、Get参数一般拼接在url的query参数里,有长度限制。

Post是不安全的、非幂等的、不可缓存的,Post的参数一般放在请求体里,相对安全。

https的过程?

https流程

https流程

一个HTTPS请求实际上包含了两次HTTP传输:

1)客户端发起一个http请求,连接到服务器的443端口。

2)服务端把自己的信息以数字证书的形式返回给客户端(证书内容有密钥公钥,网站地址,证书颁发机构,失效日期等)。证书中有一个公钥来加密信息,私钥由服务器持有。

3)验证证书的合法性

客户端收到服务器的响应后会先验证证书的合法性(证书中包含的地址与正在访问的地址是否一致,证书是否过期)。

4)生成随机密码(RSA签名)

如果验证通过,或用户接受了不受信任的证书,浏览器就会生成一个随机的对称密钥(session key)并用公钥加密,让服务端用私钥解密,解密后就用这个对称密钥进行传输了,并且能够说明服务端确实是私钥的持有者。

5)生成对称加密算法

验证完服务端身份后,客户端生成一个对称加密的算法和对应密钥,以公钥加密之后发送给服务端。此时被黑客截获也没用,因为只有服务端的私钥才可以对其进行解密。之后客户端与服务端可以用这个对称加密算法来加密和解密通信内容了。

6)客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。

7)服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。

8)然后服务器将加密后的密文发送给客户端。

9)客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。

https是绝对的安全吗?怎么保证安全性?

不是,可以被抓包。

抓包的原理?

Charles作用其实相当于拦截器,当客户端和服务器通信时,Charles其实会先接收到服务器的证书,但是它会自己生成一个证书发送给客户端(不管是Web端或App应用),也就是说它不仅仅是拦截,甚至还可以修改。

由于Charles更改了证书,所以如果你是使用的Web浏览器,需要导入相应的Charles证书,否则校验不通过会给出安全警告,必须安装Charles的证书后才能进行正常访问。

抓包的原理

1) Charles拦截客户端的请求。

2) 服务器向“客户端”(实际上是Charles)返回服务器的CA证书

3) Charles拦截服务器的响应,获取服务器证书公钥,然后自己制作一张证书,将服务器证书替换后发送给客户端。(这一步,Charles拿到了服务器证书的公钥)

4) 客户端接收到“服务器”(实际上是Charles)的证书后,生成一个对称密钥,用Charles的公钥加密,发送给“服务器”(Charles)

5) Charles拦截客户端的响应,用自己的私钥解密对称密钥,然后用服务器证书公钥加密,发送给服务器。(这一步,Charles拿到了对称密钥)

6) 服务器用自己的私钥解密对称密钥,向“客户端”(Charles)发送响应

7) Charles拦截服务器的响应,替换成自己的证书后发送给客户端

至此,连接建立,Charles拿到了服务器证书的公钥和客户端与服务器协商的对称密钥,之后就可以解密或者修改加密的报文了。

Charles作为“中间人代理”,拿到了服务器证书公钥和 HTTPS 连接的对称密钥,前提是客户端选择信任并安装Charles的CA证书,否则客户端就会“报警”并中止连接。这样看来,HTTPS还是很安全的。

怎么避免被抓包?

判断是否有代理,如果有,

1)可以取消本次的网络请求。

2)可以取消掉代理,直连服务端。

校验证书、不使用http,使用更底层的协议。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×