iOS 面试题-答案整理

  • 事件传递与视图响应链及应用

    • 事件的传递

      1. 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。

      为什么是队列而不是栈?因为队列的特点是FIFO,即先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。

      1. UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)。

      3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。

      寻找最合适的View [hitTest:withEvent:] 过程
      1.首先判断自己是否能接受触摸事件
      2.判断触摸点是否在自己身上[pointInside:withEvent:]
      3.子控件数组中从后往前遍历子控件,重复前面的两个步骤
      4.如果没有符合条件的子控件,那么就认为自己最合适处理
      不能接收触摸事件的三种情况:
      不允许交互:userInteractionEnabled = NO、隐藏hidden、透明度小于等于0.01

    @implementation WSWindow
    // 什么时候调用:只要事件一传递给一个控件,那么这个控件就会调用自己的这个方法
    // 作用:寻找并返回最合适的view
    // UIApplication -> [UIWindow hitTest:withEvent:]寻找最合适的view告诉系统
    // point:当前手指触摸的点
    // point:是方法调用者坐标系上的点
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
      // 1.判断下窗口能否接收事件
       if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil; 
      // 2.判断下点在不在窗口上 
      // 不在窗口上 
      if ([self pointInside:point withEvent:event] == NO) return nil; 
      // 3.从后往前遍历子控件数组 
      int count = (int)self.subviews.count; 
      for (int i = count - 1; i >= 0; i--)     { 
      // 获取子控件
      UIView *childView = self.subviews[i]; 
      // 坐标系的转换,把窗口上的点转换为子控件上的点 
      // 把自己控件上的点转换成子控件上的点 
      CGPoint childP = [self convertPoint:point toView:childView]; 
      UIView *fitView = [childView hitTest:childP withEvent:event]; 
      if (fitView) {
      // 如果能找到最合适的view 
      return fitView; 
      }
      } 
      // 4.没有找到更合适的view,也就是没有比自己更合适的view 
      return self;
      }
      // 作用:判断下传入过来的点在不在方法调用者的坐标系上
      // point:是方法调用者坐标系上的点
      //- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
      //{
      // return NO;
      //}
      - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
      NSLog(@"%s",__func__);
      }
      @end
    
    • 事件的响应
      1、如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
      2、在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息[touches方法],则其将事件或消息传递给window对象进行处理
      3、如果window对象也不处理,则其将事件或消息传递给UIApplication对象
      4、如果UIApplication也不能处理该事件或消息,则将其丢弃
  • KVO 实现原理
    1、当观察某对象A时,KVO动态机制会动态创建一个A类的子类NSKVONotifying_A,并且重写这个子类的setter方法、class方法、delloc、_isKVO方法
    2、当修改对象A的属性时,会调用Foundation的NSSetXXXValueAndNotify函数。
    willChangeValueForKey:
    调用父类原来的setter方法
    didChangeValueForKey
    内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)

  • NotificationCenter实现原理
    在NSNotificationCenter内部保存了两张表:一张保存添加观察者时传入了NotificationName的情况,一种保存添加观察者时没有传入NoficationName的情况。

    1. Named Table

      在named table中,NotificationName作为表的key,但因注册观察者的时可传入一个object参数用于接收指定对象发出的通知,所以value是另一张表object为key,observers为value。又一个通知可注册多个观察者,所以observes是用链表实现的。
    2. Nameless Table

      Nameless Table比Named Table要简单很多,因为没有NotificationName作为key,直接用object作为key。相较于Named Table要少一层table嵌套。
  • HTTPS的连接建立流程

    1. 客户端把TLS版本号、支持的加密算法和随机数C发送给服务器
    2. 服务器会把商定的加密算法、证书、随机数S发送给客户端
    3. 客户端会对服务器的证书进行验证。通过后会用服务器公钥来生成一个预主秘钥,并通过预主秘钥和随机数C、S来组装成会话秘钥
    4. 客户端会用服务器公钥将预主秘钥加密发送给服务器
    5. 服务器接收到加密信息后,用私钥解密得到预主秘钥。并通过预主秘钥和随机数C、S来组装会话秘钥。至此,服务器和客户端都已经知道了用于此次会话的秘钥。
    6. 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。同理,服务器收到客户端发送来的密文,用服务器密钥对其进行对称解密,得到客户端发送的数据。
  • TCP三次握手
    序列号seq:用来标记数据段的顺序
    确认号ack:期待收到对方下一个报文段的第一个数据字节的序号
    确认ACK:仅当ACK=1时,确认号字段才有效
    同步SYN:连接建立时用于同步序号。

    iOS 面试题-答案整理_第1张图片

    第一次握手:客户端进入SYN_SENT状态,并发送Syn消息给服务器,SYN标志位被置为1,同时会带上客户端分配好的Seq序列号
    第二次握手:服务器在收到Syn消息后,会进入SYN_RCVD状态,同时返回Ack消息给客户端。这一步包含两部分,一是回复客户端的Syn消息,其中ACK=1,确认号设置为客户端的Seq值+1;另是主动发送服务器的Syn消息给客户端,SYN标志位被置1,Seq号是服务器对应的序列号
    第三次握手:客户端在收到消息之后,首先会将状态变为ESTABLISHED,同时回复ACK消息给服务器。ACK状态被设置为1,确认号被设置成服务器的序列号+1。服务器在收到这个Ack消息之后,会进入ESTABLISHED状态,TCP的全双工连接建立完成。established:[ɪˈstæblɪʃt]

  • TCP四次挥手
    终止FIN:用来释放一个连接。

    iOS 面试题-答案整理_第2张图片

    第一次挥手:客户端发出连接释放报文,并且停止发送数据。FIN=1,seq为客户端的序列号,客户端进入FIN-WAIT-1(终止等待1)状态。
    第二次挥手:服务器收到连接释放报文,发出确认报文,ACK=1,确认号为客户端的Seq值+1。此时,服务端进入了CLOSE-WAIT(关闭等待)状态。客户端进入FIN_WAIT_2 (终止等待2)状态,继续等待服务器的连接释放报文。
    第三次挥手:当服务器确定数据已发送完成,就向客户端发送连接释放报文,FIN=1,Seq号是服务器的序列号,服务器进入了LAST-ACK(最后确认)状态,等待客户端的确认。
    第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,确认号=服务器的序列号+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。经过2∗MSL(最长报文段寿命)的时间后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。

  • 线程和进程的区别
    1>进程是系统进行资源分配和调度的一个独立单位,线程是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。线程基本不拥有系统资源,拥有自己的栈空间,它与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程。同一个进程中的多个线程之间可以并发执行。
    2>进程和线程的主要差别在于他们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,线程只是一个进程中的不同执行路径,有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,但在进程切换时,耗费资源较大,效率要差一些。

  • iOS 签名机制

  • 并行和并发的区别
    并发的关键是你有处理多个任务的能力,不一定要同时。
    并行的关键是你有同时处理多个任务的能力。
    它们最关键的点就是:是否是『同时』。
    并发是轮流处理多个任务,并行是同时处理多个任务

  • RunLoop项目应用

    • NSTimer滑动停止和子线程定时执行(performSelector:withObject:afterDelay)
    • 创建常驻线程
    • 监听应用卡顿睡眠之前和唤醒后的耗时
  • weak实现原理

    • 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
    • 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。
    • 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
  • 死锁条件

    • 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
    • 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
    • 不可剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
    • 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
  • Masonry实现动画
    如果其约束还没有生成的时候需要动画的话,就请先强制刷新后才写动画,否则所有没生成的约束会直接跑动画。
    因为布局的动画frame的变动是相对父视图的,所以父视图执行layoutIfNeed才会重新布局对应的UI。如果用view 执行layoutIfNeed只会从新布局view上子视图,不会产生动画的哟

  • mian函数之后启动优化

    • 第一类,如崩溃、统计SDK等,必须第一时间启动,仍然把它留在 didFinishLaunchingWithOptions 里启动。
    • 第二类,如推送、定位SDK、用户信息配置等,这些功能在用户进入 APP 主体的之前是必须要加载完的,把他放到广告页面启动。
    • 第三类,如分享、登录、支付SDK等,由于启动时不是必须的,所以可以放在第一个界面的 viewDidAppear 方法里,这里完全不会影响到启动时间。
  • 启动速度监控

    • 定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时【Time Profiler】。定时间隔设置得长了,会漏掉一些方法,从而导致检查出来的耗时不精确;
      而定时间隔设置得短了,抓取堆栈这个方法本身调用过多也会影响整体耗时,导致结果不准确。
    • 对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时。在原方法开始执行时换成执行其他你指定的方法,或者在原有方法执行前后执行你指定的方法,来达到掌握和改变指定方法的目的。
  • Auto Layout的生命周期
    Auto Layout有一整套布局引擎系统叫Layout Engine,用来统一管理布局的创建、更新和销毁。每个视图在得到自己的布局之前,Layout Engine会将视图、约束、优先级、固定大小通过计算转换成最终的大小和位置。当发生Constraints Change时,Layout Engine会重新计算布局,获取到布局后调用superview.setNeedLayout(),然后进入Deffered Layout Pass。Deffered Layout Pass主要作用是做容错处理,假如有些视图在更新约束时没有确定或缺失布局声明的话,会先在这里做容错处理。然后Layout Engine会从上到下调用layoutSubviews(),通过Cassowary算法计算子视图的位置,算出来后将子视图的frame从Layout Engine里拷贝出来,之后的流程就和手写布局一样完成绘制、渲染。

    1.Constraints Change包括添加、删除、更新、Activating、Deactivating、改变约束优先级。
    2.总体流程就是Constraints Change ->Layout Engine重新计算布局->触发superview.setNeedLayout()->Deffered Layout Pass容错处理->触发layoutSubviews()->绘制、渲染

  • 项目大了人员多了,架构怎么设计更合理

你可能感兴趣的:(iOS 面试题-答案整理)