UI要点

事件分发机制及响应者链

事件分发机制

iOS 检测到手指触摸 (Touch) 操作时会将其打包成一个 UIEvent 对象,并放入当前活动Application的事件队列,UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,UIWindow 对象首先会使用 hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。
hitTest:withEvent:方法的处理流程如下:

  • 首先调用当前视图的 pointInside:withEvent: 方法判断触摸点是否在当前视图内;
  • 若返回 NO, 则 hitTest:withEvent: 返回 nil,若返回 YES, 则向当前视图的所有子视图 (subviews) 发送 hitTest:withEvent: 消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图(后加入的先遍历),直到有子视图返回非空对象或者全部子视图遍历完毕;
  • 若第一次有子视图返回非空对象,则 hitTest:withEvent: 方法返回此对象,处理结束;
  • 如所有子视图都返回空,则 hitTest:withEvent: 方法返回自身 (self)。
    流程图如下:


    image
WeChatef25c17502fbdda8be3030cd7178401a.png

WeChat4caed0016d419c8db962d43f9b25cfb4.png

响应者链原理

iOS的事件分发机制是为了找到第一响应者,事件的处理机制叫做响应者链原理。
所有事件响应的类都是 UIResponder 的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。当发生事件时,事件首先被发送给第一响应者,第一响应者往往是事件发生的视图,也就是用户触摸屏幕的地方。事件将沿着响应者链一直向下传递,直到被接受并做出处理。一般来说,第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,就传递给它的父视图(superview)对象(如果存在)处理,如果没有父视图,事件就会被传递给它的视图控制器对象 ViewController(如果存在),接下来会沿着顶层视图(top view)到窗口(UIWindow 对象)再到程序(UIApplication 对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要有对象处理事件,事件就停止传递。
一个典型的事件响应路线如下:
First Responser --> 父视图-->VC->The Window --> The Application --> nil(丢弃)
我们可以通过 [responder nextResponder] 找到当前 responder 的下一个 responder,持续这个过程到最后会找到 UIApplication 对象。

VC生命周期

考察viewDidLoad、viewWillAppear、ViewDidAppear等方法的执行顺序。
假设现在有一个 AViewController(简称 Avc) 和 BViewController (简称 Bvc),通过 navigationController 的push 实现 Avc 到 Bvc 的跳转,调用顺序如下:
1、A viewDidLoad
2、A viewWillAppear
3、A viewDidAppear
4、B viewDidLoad
5、A viewWillDisappear
6、B viewWillAppear
7、A viewDidDisappear
8、B viewDidAppear
如果再从 Bvc 跳回 Avc,调用顺序如下:
1、B viewWillDisappear
2、A viewWillAppear
3、B viewDidDisappear
4、A viewDidAppear

列表优化技巧

cell重用

  • cell重用原理
    它的原理是,根据cell高度和tableView高度,确定界面上能显示几个cell。例如界面上只能显示5个cell,那么这5个cell都是单独创建的而不是根据重用标识符去缓存中找到的。当你开始滑动tableView时,第1个cell开始渐渐消失,第6个cell开始显示的时候,会创建第6个cell,而不是用第1个cell去显示在第6个cell位置,因为有可能第1个cell显示了一半,而第6个cell也显示了一半,这个时候第一个cell还没有被放入缓存中,缓存中没有可利用的cell。所以实际上创建了6个cell。当滑动tableView去显示第7个cell的时候,这时缓存中已经有第一个cell,那么系统会直接从缓存中拿出来而不是创建,这样就算有100个cell的数据需要显示,实际也只消耗6个cell的内存。
  • 根据cell的布局差异用不同的重用ID来进行cell的重用。

cell布局优化

  • cell布局嵌套不要过深,尽量一级。
  • 在cell初始化的时候创建好子view,尽量不要动态调整子view。
  • 尽量不要用约束。
  • 减少view个数。多用drawRect绘制元素,替代用view显示。

cell高度提前计算或者缓存

  • cell高度提前计算。比如在获取到model的时候提前计算好cell高度。
  • 高度缓存。高度算好。可以用第三方开源库UITableView-FDTemplateLayoutCell

局部更新

刷新列表的时候不要直接用reloadData。可以考虑局部更新。比如删除列表的某一行,可以调用deleteRowsAtIndexPathss删除这个cell,并且把该cell绑定的model从model数组删除。

按需加载

比如滚动不加载图片,停止滚动时候加载可见cell的图片。

避免离屏渲染

避免使用阴影、圆角、clearColor、alpha等造成离屏渲染的操作,考虑替代方案。

什么是离屏渲染?

如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer,作为像素数据存储区域,而这也是GPU存储渲染结果的地方。如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。


image
  • 在 OpenGL 中,GPU 屏幕渲染有以下两种方式:
    一、On-Screen Rendering
    即当前屏幕渲染,在用于显示的屏幕缓冲区中进行,不需要额外创建新的缓存,也不需要开启新的上下文,所以性能较好,但是受到缓存大小限制等因素,一些复杂的操作无法完成。
    二、Off-Screen Rendering
    即离屏渲染,指的是在GPU的当前屏幕缓冲区外开辟新的缓冲区进行操作。
    相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在如下两个方面:
    1、创建新的缓冲区
    2、上下文切换。离屏渲染的整个过程,需要多次切换上下文环境:先从当前屏幕切换到离屏,等待离屏渲染结束后,将离屏缓冲区的渲染结果显示到到屏幕上,这又需要将上下文环境从离屏切换到当前屏幕。
  • CPU 渲染和离屏渲染的区别
    由于GPU的浮点运算能力比CPU强,CPU渲染的效率可能不如离屏渲染。但如果仅仅是实现一个简单的效果,直接使用 CPU 渲染的效率又可能比离屏渲染好,毕竟普通的离屏渲染要涉及到缓冲区创建和上下文切换等耗时操作。对一些简单的绘制过程来说,这个过程有可能用CoreGraphics,全部用CPU来完成反而会比GPU做得更好。一个常见的 CPU 渲染的例子是:重写 drawRect 方法,并且使用任何 Core Graphics 的技术进行了绘制操作,就涉及到了 CPU 渲染。整个渲染过程由 CPU 在 App 内同步地完成,渲染得到的bitmap最后再交由GPU用于显示。总之,具体使用 CPU 渲染还是使用 GPU 离屏渲染更多的时候需要进行性能上的具体比较才可以。
  • iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染。
    iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。

造成离屏渲染原因

  • shouldRasterize(光栅化)。
  • masks(遮罩)。
  • shadows(阴影)。
  • edge antialiasing(抗锯齿)。
  • group opacity(不透明)
  • clearColor、alpha等操作。

解决方案

  • clearColor可以通过直接设置颜色来解决。
  • alpha为0时候用hidden替换。
  • 圆角、边框解决方案:1、UIBezierPath 2、使用Core Graphics为UIView加圆角 3、直接处理图片为圆角 4、后台处理圆角
  • 阴影解决方案:shadowPath替换。
  • 尝试开启CALayer.shouldRasterize。
  • 对于不透明的View,设置opaque为YES,这样在绘制该View时,就不需要考虑被View覆盖的其他内容(尽量设置Cell的view为opaque,避免GPU对Cell下面的内容也进行绘制)

图片子线程预加载及预处理

  • 图片子线程异步下载。
  • 图片子线程处理。比如对于圆角图片,可以让后台传圆角图片,也可以在子线程生成圆角图片,也可以用UIBezierPath生成圆角;在子线程缩放图片然后加载到图片控件上。
  • 图片按需下载。只下载显示的cell的图片。

异步绘制

  • 在子线程绘制好内容,主线程更新。
  • 考虑用 texture来做异步绘制。

分页加载

当有大量数据时采用分页加载。

参考资料:
iOS 保持界面流畅的技巧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容