UI

作者: beginBird | 来源:发表于2021-09-23 15:47 被阅读0次

事件处理

事件产生

  1. 当用户点击屏幕,IOKit收到屏幕操作,将这次操作封装为IOHIDEvent对象,通过mach port将事件发送给SpringBoard。
  2. SpringBoard是iOS系统的桌面程序,SpringBoard收到mach port发送来的事件,将事件发送给在前台显示的程序。
  3. 唤醒此程序的main runloop并交给source1处理,处理内部会将事件交给source0处理,并调用source0的__UIApplicationHandleEventQueue()函数,在其中将事件转换为UIEvent。
  4. 调用UIApplocation的sendEvent:方法

事件传递&响应

传递:

  1. UIApplication接收到事件,将事件传递给keyWindow。
  2. keyWindow遍历subViews的hitTest:withEvent:方法,找到点击区域合适的试图来处理。
  3. UIView的子视图也会遍历其subViews的hitTest:withEvent:方法。
  4. 找到点击区域内,且处于最上方的视图,将视图逐步返回给UIApplication。
  5. 在查找第一响应者的过程中,形成了响应链。

响应:

  1. 应用调用第一响应者处理事件,
  2. 如果第一响应者不能处理事件,则调用其nextResponder方法,一直找到响应链中能够处理该事件的对象。
  3. 最后到UIApplication后仍然没有处理该事件的对象,则该事件被废弃
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha <= 0.01 || self.userInteractionEnabled == NO || self.hidden) {
        return nil;
    }
    
    BOOL inside = [self pointInside:point withEvent:event];
    if (inside) {
        NSArray *subViews = self.subviews;
        // 对子视图从上向下找
        for (NSInteger i = subViews.count - 1; i >= 0; i--) {
            UIView *subView = subViews[i];
            CGPoint insidePoint = [self convertPoint:point toView:subView];
            UIView *hitView = [subView hitTest:insidePoint withEvent:event];
            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
    return nil;
}

事件拦截

如果想让指定试图来响应事件,不再向其子试图继续传递事件,可以通过重写如下

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return self;
}

事件转发

SuperView的SubView超出了其视图范围,如果点击SubView在试图外面的部分则不能响应时间。可以通过重写pointInside:withEvent:方法,将相应区域扩大为虚线区域,包括SuperView的所有子试图,即可以让子试图响应事件。

事件逐级传递

如果想让响应者中每一级都可以响应事件,可以在每级UIResponder中都实现touches并调用super。

UIControl > 手势 > UIResponder

iOS渲染原理 & UI的刷新原理

CPU 和 GPU 的设计目的分别是什么?

CPU是运算核心和控制核心,需要很强的运算通用型,兼容各种数据类型。
GPU则面对的是类型统一,更大的运算任务。

计算机图像渲染流水线的大致流程是什么?

Application(应用处理阶段:得到图元) -> Geometry Processing(几何处理阶段:处理图元) -> Rasterization(光栅化阶段:图元转换为像素) -> Pixel Processing(处理像素,得到位图)

Framebuffer 帧缓冲器的作用是什么?

存储渲染之后的像素信息,方便控制器读取。

Screen Tearing 屏幕撕裂是怎么造成的?

控制器读取帧缓存器上半部时,新的帧以及渲染完成放入帧缓存器中,控制器读取的不是同一帧图像,导致屏幕撕裂。

如何解决屏幕撕裂的问题?

引入垂直同步Vsync+双缓冲机制Double Buffering
在屏幕扫描上一帧加载完成之前新渲染的帧数据放入back buffer里,当当前帧加载完成后发出Vsync信号去通知控制器去将back buffer中的内容置换到frame buffer中。

掉帧是怎么产生的?

如果在接收到Vsync信号时CPU和GPU还没有渲染好新的位图,控制器就不会去替换frame buffer中的位图。这时候屏幕就会重新扫描呈现出上一帧一摸一样的画面,这就是掉帧。

CoreAnimation 的职责是什么?

Core Animation是一个复合引擎,主要职责包括:渲染 构建 实现动画,尽可能快的组合屏幕上的CALayer,并且被存储为树状层级结构

UIView 和 CALayer 是什么关系?有什么区别?

创建UIView都会自动创建一个CALayer,为自身提供存储bitmap的地方,并将自身固定设置为CALayer的代理。

  1. 我们对 UIView 的层级结构非常熟悉,由于每个 UIView 都对应 CALayer 负责页面的绘制,所以 CALayer 也具有相应的层级结构。
  2. CALayer 继承自 NSObject,UIView 由于要负责交互事件,所以继承自 UIResponder。
  3. 因为 UIView 只对 CALayer 的部分功能进行了封装,而另一部分如圆角、阴影、边框等特效都需要通过调用 layer 属性来设置。
  4. CALayer 不负责点击事件,所以不响应点击事件,而 UIView 会响应。

为什么会同时有 UIView 和 CALayer,能否合成一个?

  1. 这样设计的主要原因就是为了职责分离,拆分功能,方便代码的复用。
  2. iOS 有 UIKit 和 UIView,OS X 则是AppKit 和 NSView。

渲染流水线中,CPU 会负责哪些任务?

离屏渲染为什么会有效率问题?

离屏渲染需要先额外创建离屏缓冲区,将提前渲染好的内容放入,之后将Offscreen Buffer中的内容进一步叠加 渲染 完成后将结果切换到FrameBuffer中。

  1. Offscreen Buffer本身需要额外空间。
  2. 离屏渲染的开销很大,一旦离屏渲染的内容过多,很容易造成掉帧。

什么时候应该使用离屏渲染?

  1. 一些特殊效果需要额外使用Offscreen Buffer来保存渲染的中间状态。(系统自动触发:阴影 圆角)
  2. 效率目的,可以将内容提前渲染保存在Offscreen Buffer中,达到复用的目的。(主动触发:通过CALayer shouldRasterize)

shouldRasterize 光栅化是什么?

开启光栅化后,会触发离屏渲染,Render Server会强制将CALayer的渲染位图结果bitmap保存起来,下次可直接复用。

有哪些常见的触发离屏渲染的情况?

  1. 使用了mask的layer(layer.mask)
  2. 需要进行裁剪的layer(layer.masksToBounds/view.clipsToBounds)
  3. 设置了组透明度为YES,并且透明度不为1的layer(layer.opacity)
  4. 添加了投影的layer(layer.shadow)
  5. 采用了光栅化的layer(layer.shouldRasterize)
  6. 绘制了文字的layer(UILabel, CATextLayer, Core Text)

圆角触发的离屏渲染有哪些解决方案?

  1. 使用带圆角的图片。
  2. 在添加一个和背景相同的遮罩mask覆盖在最上面,盖住四个角。
  3. UIBezierPath贝塞尔曲线。
  4. CoreGraphics,重写drawRect

重写 drawRect 方法会触发离屏渲染吗?

不会,虽然drawRect会将GPU中的渲染操作放入CPU中来做,并且需要额外开辟一个空间来做,这和标准意义上的离屏渲染不同。

drawrect & layoutsubviews调用时机

layoutsubviews:

  1. addSubView
  2. 修改frame
  3. 滚动UISCrollView
  4. 直接调用setNeedsLayout

drawrect:

  1. Controller->loadView 和 -> viewDidLoad之后
  2. sizeToFit之后
  3. 设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:
  4. 直接调用setNeedsDisplay

Talbleview 的性能优化

  1. 快速滑动时先不加载TalbleviewCell上的图片,等将要停止时再去加载当前Cell附近Cell的图片。
  2. 监听RunLoop闲时再去做图片的加载。

AutoLayout的原理,性能如何

根据约束条件算出frame,在复杂界面异常差。

隐式动画 & 显示动画区别

imageName & imageWithContentsOfFile区别

imageName:

  1. 系统会缓存加载的图片

imageWithContentsOfFile:

  1. 不会被缓存,每次都需要重新加载

相关文章

网友评论

      本文标题:UI

      本文链接:https://www.haomeiwen.com/subject/ftyzgltx.html