[特斯拉组件]ios高性能PageController
本文来自于腾讯Bugly公众号(weixinBugly),作者:sparrowchen,未经作者同意,请勿转载,原文地址:
http://mp.weixin.qq.com/s/hBgvPBP12IQ1s65ru-paWw
1.组件介绍
Page是企鹅FM研发的分页组件,包括支持分页非交互切换(通过方法调用导航切换)和交互切换(屏幕的手势滑动),多个分页Controller和View的管理。
1.1需求背景
为什么弃用UIPageViewController,首先介绍一下UIPageViewController,这是系统为开发者定制的分页组件,提供了两种分页切换的效果,一是滑动 二是翻页。且提供了前后切换的回调。
a) UIPageViewController在iOS8以下的系统运行是有问题的,可以参考stackFlow上的症状描述https://stackoverflow.com/questions/12939280/uipageviewcontroller-navigates-to-wrong-page-with-scroll-transition-style/12939384#12939384
This is actually a bug in UIPageViewController. It occurs only with the scroll style (.Scroll) and only after calling setViewControllers:direction:animated:completion: with animated:YES. Thus there are two workarounds:
Don't use UIPageViewControllerTransitionStyleScroll.
Or, if you call setViewControllers:direction:animated:completion:, use animated:NO.
To see the bug clearly, call setViewControllers:direction:animated:completion: and then, in the interface (as user), navigate left (back) to the preceding page manually. You will navigate back to the wrong page: not the preceding page at all, but the page you were on when setViewControllers:direction:animated:completion: was called.
The reason for the bug appears to be that, when using the scroll style, UIPageViewController does some sort of internal caching. Thus, after the call to setViewControllers:direction:animated:completion:, it fails to clear its internal cache. It thinks it knows what the preceding page is. Thus, when the user navigates leftward to the preceding page, UIPageViewController fails to call the dataSource method pageViewController:viewControllerBeforeViewController:, or calls it with the wrong current view controller.
大意是说使用.Scroll的时候,UIPageViewController做了内部缓存的排序,当调用
setViewControllers:direction:animated:completion:
时 它认为自己知道了前一个的分页存在,当调用前一个页面的时候,就不会去调用dataSource的方法。
b) UIPageViewController的DataSource和Delegate的接口过于简单,对于比较复杂的情况(比如除了分页以外还有其他View的情况下)无法处理。参照下面的例图,我有一个tab下面有小黄条,跟着手势横向滑动的同时也横向滑动,这里系统的UIPageViewController无法支持。其外,我还需要子页面纵向滑动时候去修改Cover和Tab的frame。所以UIPageViewController无法满足比较复杂的需求。
c) 低配的机器会产生卡顿问题,因为系统的UIPageViewController,在快速切换的时候,会释放掉不用的页面,所以在快速回切的时候会造成卡顿,可以参考下面的性能测试。
综上所述,弃用了系统的UIPageViewController。
1.2使用说明
使用非常简单,继承组件的类,实现相应的delegate和datasourc就可以了。
Page的例图如下:
页面层次关系如下:
图中由一个图片,3个栏目 (详情,节目,评论)和一个List组成。可以分为三个层次,Cover,Tab和Page。
Page组件层次关系如下,
图中的ShowListController是节目分页,AlbumListController是专辑分页.
2.组件架构设计
2.1 架构介绍
类图如下:
简要说明下各个协议的作用:
FMPageDataSource, 提供子页面,子页面的个数,子页面展示的frame给PageController。
FMPageDelegate, 提供页面交互切换和非交互切换的回调给上层以及页面的纵向滑动和横向滑动的contentoffset给上层。
FMTabDataSource, 提供TabView的具体展示效果。
FMTabDelegate, 提供TabView的点击响应给上层。
FMCoverController, 提供CoverView给CoverController.
其中,FMTabController默认遵循FMTabDataSource,FMTabDelegateSource,FMPageDataSource,FMPageDelegate协议。FMCoverController遵循FMCoverDatasource协议。
2.2 接口设计
接口遵循高内聚和低耦合的特性,只把Delegate和DataSource开放给上层,同时做接口分离,把Page,Tab,Cover特性的分离。 代码如下:
@interface FMTabController : FMBusinessViewController <FMPageControllerDataSource, FMPageControllerDelegate, FMTabDataSource, FMTabDelegate>
@interface FMCoverController : FMTabController <FMCoverDataSource>
2.3 Child页面的生命周期管理和切换。
1.UIScrollView支持分页效果,手势处理及交互操作多个回调方法可以实现页面的切换效果。
2.生命周期管理有两种方式 a.频繁地add/remove ChildController b.使用下面的代码实现生命周期的管理:
1)shouldAutomaticallyForwardAppearanceMethods
2)beginAppearanceTransition: animated:
3)endAppearanceTransition
a.会产生一个重大缺陷,就是频繁切换的卡顿问题。
b.不需要频繁地去调用add/remove,1)方法避免了 add/remove产生的生命周期,2)和3)保证了开发者可以自己控制ChildController的生命周期。
Page的生命周期图如下:
初次或者reloadPage
交互切换和非交互切换
2.4 性能问题扩展
以下通过Iphone5 模拟器 10.3系统,与UIPageViewController做了性能上的对比。
UIPageViewController 快速切换内存占用情况
UIPageViewController 快速切换GPU占用情况
Page组件快速切换内存占用情况
Page组件快速切换GPU占用情况
从上图中内存占用图标的波动情况可以看出UIPageViewController在快速切换的时,会尽可能快地释放掉不用的controller及其view(主要是view)以保证内存占用较小,所以图标指标先才会频繁的波动,与UIPageViewController作对比,Page组件用空间换时间的策略避免页面卡顿。
3.技术实现的难点
从技术上看,可以分为以下四个点
3.1 接口的设计。
接口的设计,是整个架构的核心,如果开始设计不好,会导致后续的扩展就是加属性和加方法,导致代码越来越庞大,以致无法维护,所以尽量保证简洁,职能单一,可扩展。
起初为了让delegate和datasource可以从Controller分离出去,把delegate和datasource都暴露了出去,但这样相当于多了5个属性,对于上层来说并不便于理解这些接口,仿照UITableViewController,由继承的方式实现这些协议,让接口更加简洁。
3.2 页面纵向滑动跟随Tab和Cover一起滑动。
通过上面的动态图,可以知道,Page组件有这样一个功能,子页面纵向滑动会跟随Tab和Cover一起向上滑动,其中cover的滑动的实现是监听ChildController的ScrollView的contentOffset,修改Tab的height或y。Scrollview的滑动有一个难点,怎样保证ScrollView的向下滑动的反弹处紧贴Tab,而Scrollview又可以向上滑动到导航栏。
首先Scrollview的可见范围是整屏的,也就是设置frame为整屏,Scrollview滑动的范围,就由ContentInset,ContentOffset 共同决定。因为我们知道UIScrollView的滑动范围会紧贴scrollView的bounds。所以首先,修改ContentInset的Top为-tabH-tabY,可以保证向下滑动到Tab的下边缘处反弹,又由于frame是整屏的,向上滑动时候就可以滑动导航栏,代码如下:
scrollView.contentInset = UIEdgeInsetsMake([self.dataSource pageTop], contentInset.left, contentInset.bottom, contentInset.right);
scrollView.frame = CGRectMake(0,0,Screen_Width,Screen_Height)
其中的pageTop就是tab的下边缘处。
3.3不相邻页面切换的问题
不相邻页面的非交互切换会闪过中间的页面,产生不好的用户体验,本组件的解决方法是
非交互切换,模拟切换的动画,这里需要考虑的一个复杂情况是第一次动画还未结束就开始第二次,这时候需要提前结束第一次动画。修改后的效果图如下,
3.4平衡性能的问题。
因为Page要管理多个controller和view,如果子页面到1000,甚至10000个怎样去处理。比如微信阅读的一本书就可能有10000页。所以这里如果全部都保存就可能产生一个问题,内存会不会过大。
观察UIPageViewController,它到一定的内存限制,会主动去释放很久没翻过的页面。所以这里,可以使用LRUCache的机制,只保存一定数量的页面。由于本应用并不涉及到过多的子页面,考虑的时间花销和内存,全部保存了所有页面。
demo地址:https://github.com/xichen744/SPPage
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:
http://mp.weixin.qq.com/s/hBgvPBP12IQ1s65ru-paWw
[特斯拉组件]ios高性能PageController的更多相关文章
- 封装 React Native 原生组件(iOS / Android)
封装 React Native 原生组件(iOS / Android) 在 React Native中,有很多种丰富的组件了,例如 ScrollView.FlatList.SectionList.Bu ...
- iOS高性能图片架构与设计
版权声明:本文由柯灵杰原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/157 来源:腾云阁 https://www.qclo ...
- iOS高性能设置圆角
自建一个分类可以设置. -(void)cornerImageWithSize:(CGSize)size fillColor:(UIColor *)fillColor completion:(void( ...
- 基于APNs最新HTTP/2接口实现iOS的高性能消息推送(服务端篇)
1.前言 本文要分享的消息推送指的是当iOS端APP被关闭或者处于后台时,还能收到消息/信息/指令的能力. 这种在APP处于后台或关闭情况下的消息推送能力,通常在以下场景下非常有用: 1)IM即时通讯 ...
- 大话大前端时代(一) —— Vue 与 iOS 的组件化
序 今年大前端的概念一而再再而三的被提及,那么大前端时代究竟是什么呢?大前端这个词最早是因为在阿里内部有很多前端开发人员既写前端又写 Java 的 Velocity 模板而得来,不过现在大前端的范围已 ...
- fir.im Weekly - 热门 iOS 第三方库大盘点
本期 fir.im Weekly 收集的热度资源,大部分关于Android.iOS 开发工具.源码和脑洞大开的 UI 动画,希望给你带来更多的工作创意与灵感. 盘点国内程序员不常用的热门iOS第三方库 ...
- 第18月第2天 ios博客
1. github https://githuber.cn/search?language=Objective-C https://www.jianshu.com/u/815d10a4bdce htt ...
- react-native 常用组件的用法(二)
ScrollView组件 能够调用移动平台的ScrollView(滚动视图)的组件,同时还集成了触摸锁定的“响应者”系统.注意一定要给scrollview一个高度,或者是他父级的高度. 常用方法 on ...
- MMKV 多进程K-V组件 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
随机推荐
- Django——模板层(template)(模板语法、自定义模板过滤器及标签、模板继承)
前言:当我们想在页面上给客户端返回一个当前时间,一些初学者可能会很自然的想到用占位符,字符串拼接来达到我们想要的效果,但是这样做会有一个问题,HTML被直接硬编码在 Python代码之中. 1 2 3 ...
- Visual Studio中让一个JS文件智能提示另一个JS文件中的成员
当一个Web页面引用了两个JS文件(假如分别叫common.js和JScript1.js),如果JScript1.js中需要调用大量的common.js中的方法,这时候在JScript1.js中智能提 ...
- JPA + SpringData 操作数据库 ---- 深入了解 SpringData
原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7735616.html ------------------------------------ ...
- Cocos 2d-X Lua 游戏添加苹果内购(二) OC和Lua交互代码详解
这是第二篇 Cocos 2d-X Lua 游戏添加苹果内购(一) 图文详解准备流程 这是前面的第一篇,详细的说明了怎样添加内购项目以及填写银行信息提交以及沙盒测试员的添加使用以及需要我们注意的东西,结 ...
- C#用到windows 消息列表Message类MSG的id代号
C# Constants:private const UInt32 WM_ACTIVATE = 0x0006;private const UInt32 WM_ACTIVATEAPP = 0x001C; ...
- LeetCode 495. Teemo Attacking (提莫攻击)
In LOL world, there is a hero called Teemo and his attacking can make his enemy Ashe be in poisoned ...
- 快速部署MongoDB
MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案.本文安装的版本为3.0,其他版本可对照. 设置mongodb repo vi /e ...
- python爬虫如何入门
学爬虫是循序渐进的过程,作为零基础小白,大体上可分为三个阶段,第一阶段是入门,掌握必备的基础知识,第二阶段是模仿,跟着别人的爬虫代码学,弄懂每一行代码,第三阶段是自己动手,这个阶段你开始有自己的解题思 ...
- POJ2318 TOYS(叉积判断点与直线的关系+二分)
Calculate the number of toys that land in each bin of a partitioned toy box. Mom and dad have a prob ...
- 使用js编写一个简单的运动框架
下班后,,没事捣鼓捣鼓个人的小爱好. 首先,说明我的这个运动框架(css所有属性)也是常见的框架一种,健壮性并不是太好,对于新手学习倒是挺好,,若是大神,老司机请拐弯. 上来,我们先定义一个区块,然后 ...