最近在掘金上分享了一篇关于性能优化文章iOS性能优化系列篇之“优化整体原则”,第一次发表文章在网上,结果收到了好多人的正面的反馈,受到了一点点鼓舞,这篇文章是好久之前组内作的一个分享,整理成文字分享给你们,里面主要内的是一些关于iOS布局经常使用的技术和多分辨率适配的一些方案,还略微延伸到其余平台的多分辨率适配方案,内容大可能是一些资料的整理和综述,但愿你们可以喜欢。css
定义:计算绝对坐标,赋值给UIView的frame属性。html
优势:使用简单直接,无学习成本。android
缺点:不够灵活,在多分辨率适配或者横竖屏变化时,UIView很难根据要求自动改变布局,若是要写出adaptive的界面,须要根据不一样屏幕方向、分辨率、机型等信息,相应的写不少frame变化的代码。ios
第三方库:设置frame的一些方便的第三方库, 在必定程度上解决了这些问题。好比git
1.Facade连接github
2.ios-view-frame-builder连接web
3.Neon连接, swift版spring
上诉第三方库简化了手写frame的流程,并提供了好比相对位置的便捷写法还有对于view group的布局方法。可是提供给没有适配屏幕的功能。express
我的建议:json
1、 子view的布局相关的代码写在layoutSubview里面。我认为这样的好处有:
1.布局代码位置统一,其余人接手新代码的时候直接在layoutSubview就能够找到布局信息。若是把布局都写到initWithFrame中,一个问题是若是子view依赖父view的bounds,这个时候每每父view尚未获得正确的frame,好比即便你设置了UITableViewCell的高度为80,但此时你取到的仍是默认的44。还一个问题是若是代码里有其余影响布局的逻辑,好比点击一个button,另一个view变小,这样布局代码散就会落在不一样的位置,带来Shotgun代码,若是把布局代码统一放到layoutSubview里面,在点击button的时候修改下状态量,再setNeedsLayout就可在layoutSubview根据状态不一样采用不一样的布局。
2.布局自动调用,能够实现简单的自动适配等效果。好比横竖屏的布局差别不是特别大,同一套frame布局能够实现适配,转屏的时候自动就能够从新layout,无需本身在转屏时机手动从新布局。
2、布局不要使用绝对坐标。最好结合使用屏幕宽高、相对位置(好比A的左边距离B的右边10,A的center X与父View的center X相同等)、百分比(好比A的宽度和父View的宽度相同或者是其一半)、Edge Insets(好比A的right为10)等,这样在适配要求不高的状况下也能够实现较好的适配效果。
定义:Springs&Struts决定了当UIView的父View的bounds变化时,其自身的坐标如何变化:是有flexible or fixed margins (the struts), width and height (the springs)。UIView的autoresizingMask属性有以下枚举值:UIViewAutoresizingFlexibleLeftMargin、UIViewAutoresizingFlexibleWidth、UIViewAutoresizingFlexibleRightMargin、UIViewAutoresizingFlexibleTopMargin、UIViewAutoresizingFlexibleHeight和UIViewAutoresizingFlexibleBottomMargin(这些值能够取或进行合并)。
优势:简单,也便是使用简单方便。好比经常使用若是想使子view的size和父view的size始终相同,能够用UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight便可。
缺点:缺点也是简单,这个简单是其应用场景的简单,仅仅能使用中一些简单的布局case,从定义上就能够看出Springs&Struts描述的是子view和父view的关系,没法实现子view之间有布局相关性的复杂场景。而且没有涉及子view变化的时候具体应该变化多少。
好比想使用Springs&Struts实现以下视图的布局,示例来源连接。全部的间距是20, 上面两个view的宽度相同,全部view的高度相同:
那么若是使用Springs&Struts:
1.上方绿色view设置以下:
2.上方黄色view设置以下:
3.下面蓝色view设置以下:
通过上述对Springs&Struts的设置,转屏后,效果仍然错误,效果以下:
若是想正确实现要求的布局,只能在转屏的时候本身手动修改frame才能实现。
优势:
1.Designing by intent。 Autolayout最大的优势就是摆脱之前手写frame的烦恼,用constraints来描述view自己或者view和view之间的布局关系。这是彻底不一样的思惟也便是Designing by intent。一旦constraints创建好,剩下的都由Autolayout来替咱们解决。
2.国际化。同一个表述在不一样国家的语言中,文字的长度差别很大,使用Autolayout能够很好的解决这个问题。
3.多分辨率适配,不管分辨率、机型、横竖屏怎么变化,Autolayout都会替咱们自动将view的frame最终设置好。
5.Dynamic Type。有关Dynamic Type的相关知识能够参考Supporting Dynamic Type。
缺点:
1.相对复杂,学习曲线较大,坑比较多。若是使用过Autolayout,会发现其中有不少坑,好比UIScrollView约束设置连接,忘记设置translatesAutoresizingMaskIntoConstraints为NO, Autolayout的debug调试连接,作动画等等。(其实这些也并不算什么缺点,只不过须要时间去学习和理解)。
2.大多数老工程都是基于传统的frame来进行布局,都有本身的一套布局方案,很难迁移到新技术。
3.在view多的时候或者对性能要求比较高的场合下性能会出现问题。这才是autolayout在应用到实践过程当中最大的问题。这两篇文章定量的评测了autolayout在不一样数量view下的性能表现Auto Layout Performance on iOS 和Optimising Autolayout 。而在实际应用过程当中,的确也会有性能问题。搜狗iOS输入法在最初适配不一样键盘尺寸的按键布局时,使用了 Autolayout 的解决方案,影响了键盘调起速度,后续针对键盘调起速度的优化过程当中,经过Instruments的Time Profiler才定位到该问题,参考搜狗输入法性能优化实践。还有XING app的开发者在他们在项目初期,在列表的布局上使用了Autolayout。带来了没法解决的性能问题,因此放弃使用Autolayout,博客连接Auto Layout in Cells – Don’t!。还有LinkedIn团队也遇到了一样的问题,他们为此开发了LayoutKit来代替Autolayout,他们在介绍LayoutKit的时候,这样写道"LinkedIn created LayoutKit because we have found that Auto Layout is not performant enough for complicated view hierarchies in scrollable views."
使用Autolayout经常使用的几种方式:
1.直接使用 NSLayoutConstraint的constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:方法。例如:
2.使用NSLayoutAnchor。例如:
3.Visual Format Language(VFL)。例如:
4.使用interface builder, 从Apple对interface builder的不断改进还有WWDC中相关视频等信息中,咱们能够发现Apple认为使用interface builder建立constraints才是best practice。这就把话题引到了一直存在的很大争议,究竟是用纯手工写布局好仍是使用使用interface builder好。这里有喵神写的博客代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧和唐巧的文章iOS 开发中的争议(二)。具体如何选择你们可本身判断。
5.其余第三方库。由于你们对苹果提供的Autolayout的API不满意,Github上有不少针对对Autolayout封装的库,使用起来大大减小了代码量,很好上手。好比其中Objc写的、最为普遍使用的Masonry和PureLayout,swift语言写的SnapKit和Cartography。有兴趣的可本身了解下。
6.Stack Views. UIStackView(iOS9+)class provides a streamlined interface for laying out a collection of views in either a column or a row. Stack views let you leverage the power of Auto Layout, creating user interfaces that can dynamically adapt to the device’s orientation, screen size, and any changes in the available space. The stack view manages the layout of all the views in its arrangedSubviews property. These views are arranged along the stack view’s axis, based on their order in the arrangedSubviews array. The exact layout varies depending on the stack view’s axis, distribution, alignment, spacing, and other properties. 其实Stack View和Android的Linear Layout相似,都是提供在同一方向(横向或者纵向)布局多个view的便捷方法。我以为iOS中不只仅是UIStackView,UICollectionView使用得当,在特定的应用场景下也能够简单地实现适配不一样屏幕的做用。
学习Autolayout。能够参考Apple的GuideAuto Layout Guide。还有一些优秀的博文,好比objc上的Advanced Auto Layout Toolbox还有raywenderlich上的Auto Layout Tutorial in iOS 9 Part 1: Getting Started和Auto Layout Tutorial in iOS 9 Part 2: Constraints还有WWDC上关于Autolayout相关的视频等等。
除了上面苹果官方提供的两种布局技术,还有其余的方案,好比:
CSSLayout,是Facebook开源的对flexbox跨平台的实现。他主要的目的是使web, android, iOS, 和windows的开发者使用同一种布局技术来实现布局,节省学习成本,尤为是对web开发者。
ComponentKit,也是是Facebook开源的项目。官方介绍目前Facebook的iOS app大多都使用componentkit。 componentkit使用functional, declarative的方式去构建UI。官方介绍其优势有:
1."ComponentKit takes a functional, declarative approach to building UI and emphasizes a one-way data flow from immutable models to immutable components that describe how views should be configured" "It was built to power Facebook's News Feed and is now used throughout the Facebook iOS app."
2."It performs layout on a background thread, creates the minimal view hierarchy to render the components on screen and has intelligent view reuse. This results in great scroll performance and a snappy system overall."
LayoutKit 官方介绍"LinkedIn created LayoutKit because we have found that Auto Layout is not performant enough for complicated view hierarchies in scrollable views."而且和autolayout作了对比:
LayoutKit has many benefits over using Auto Layout:
1.Fast: LayoutKit is as fast as manual layout code and significantly faster than Auto Layout.
2.Asynchronous: Layouts can be computed in a background thread so user interactions are not interrupted.
3.Declarative: Layouts are declared with immutable data structures. This makes layout code easier to develop, code review, debug, and maintain.
4.Cacheable: Layout results are immutable data structures so they can be precomputed in the background and cached to increase user perceived performance.
MyLayout, 这个是国内的iOS开发者写的,其我的在主页上介绍项目的亮点在于“一套功能强大的iOS界面布局库,他不是在AutoLayout的基础上进行的封装,而是一套基于对frame属性的设置,并经过重载layoutSubview函数来实现对子视图进行布局的布局框架。所以能够无限制的运行在任何版本的iOS系统中。其设计思想以及原理则参考了Android的布局体系和iOS自动布局以及SizeClass的功能,经过提供的:线性布局MyLinearLayout、相对布局MyRelativeLayout、框架布局MyFrameLayout、表格布局MyTableLayout、流式布局MyFlowLayout、浮动布局MyFloatLayout、路径布局MyPathLayout七个布局类,以及对SizeClass的支持,来完成对界面的布局。MyLayout具备功能强大、简单易用、几乎不用设置任何约束、能够完美适配各类尺寸的屏幕等优点。”
上面介绍了一些第三方的布局方案,你们有兴趣能够研究下,特别是ComponentKit。不过在实际应用的过程当中对这些第三方库须要谨慎使用,须要完全理解其原理并针对项目特色进行调研,要在使用成本和带来的收益之间进行平衡。
因为早期iOS平台上屏幕分辨率种类较少,即便到后期和Android或者web相比也是很少,再加上目前市面上的app大多仅支持iPhone且为竖屏,致使开发者对多分辨率适配并不重视。相关适配的文章也很少。在适配方面咱们能够借鉴下其余平台的解决方案,开阔下本身的思路。因为我没有作深刻的学习,仅作简单介绍,有兴趣的同窗能够自行深刻了解。
和iOS相似,Android声明布局也大致有两种途径:参考
1.Declare UI elements in XML.(在iOS平台,Xcode以XML格式储存xib和storyboard文件内容。编译时,Xcode将xib和storyboard文件编译成二进制文件,即nib文件。运行时,nib文件会被载入并实例化来建立新的视图。)
2.Instantiate layout elements at runtime.
具体的布局的手段有参考:
Android和iOS是目前市场份额最大的两个移动平台,因为Android平台的开放性,致使其碎片化极为严重,固然随之而来的屏幕的尺寸也十分繁杂(如图),所以多分辨率的适配工做对于Android平台来讲应该是一个极为重要的事情。做为iOS开发者,咱们能够了解下Android平台的适配策略,看是否有能够借鉴的地方。
The foundation of Android's support for multiple screens is its ability to manage the rendering of an application's layout and bitmap drawables in an appropriate way for the current screen configuration. The system handles most of the work to render your application properly on each screen configuration by scaling layouts to fit the screen size/density and scaling bitmap drawables for the screen density, as appropriate. To more gracefully handle different screen configurations, however, you should also:
Best Practices
从上面介绍的官方适配参考来看,Android平台适配的整体思路就是将设备按照屏幕的特色分红不一样的类别,同类别的屏幕具备差异不大的适配特性,而后经过Qualifier来为不一样的屏幕分类提供不一样的资源(layout,image等)。整体思路和iOS平台有些类似也有不一样,好比Android平台使用的Qualifiers,iOS平台也会根据不一样屏幕给出2X 3X、设备类型(iPhone iPad)、size class、memory等属性对应的图片,只不过Android平台的Qualifiers更丰富,对设备区分的更详细。还有xml的使用上,我我的以为Android平台的更轻量级一些(虽然Android也有和iOS相似的interface builder), Android平台对xml的定位我的感受更像CSS。我以为这个是咱们作iOS开发能够借鉴的一个地方,用比较轻量级的配置文件(xml、json或plist)来将客户端布局的部分和业务逻辑分离开,增长其扩展性。因为interface builder没法动态下发,而使用xml不只能够完成由数据驱动UI,还能够动态下发布局,特别适合须要支持不一样主题,或者有着比较强烈的界面动态化需求的应用。
以前介绍了iOS平台经常使用布局技术,这些都是布局的工具,单一的布局技术并不能解决多分辨率适配的问题,要适配多分辨率须要将多种相关布局技术结合起来并须要采用必定的适配策略。下面是经常使用的适配策略:
Best Practice
1.Interface Builder. Apple一直竭尽全力的推广和改进Interface Builder,Interface Builder可很方便的设置constraints,特别从iOS8的Size Class概念的提出,Interface Builder在Adaptive User Interfaces的优点更加明显。具体能够参考Adaptive Layout Tutorial in iOS 9: Getting Started
2.Image Asset。使用Image Asset可为不一样的设备提供不一样的图片素材,以达到最优的用户体验。下图展现了目前Image Asset能够配置的选项。配置好后在对应的目录下实际上是一个json配置文件(咱们也能够参考这个方案,本身写配置文件,来支持更复杂的图片配置逻辑),编译后生成.car文件。
其中有几个选项你们能够关注下,一个是Width还有Height。这个就是能够指定Size Class的选项。还有Scale Factors,这个选项有Single Vector或Vector With Overrides(Vector With Overrides是Single Vector的加强, 能够在放置完矢量图以后继续放置@1x、@2x和@3x的png格式的图片。放置的png会优先覆盖矢量图, 未放置对应倍率图片的设备才会使用矢量图对应生成的图片),这个选项主要是为了支持矢量图,能够只放一张PDF的图片,编译后Xcode会自动生成1x,2x,3x的图,须要注意的是使用他并不会减小包的体积,由于不是运行时的特性。还有Slicing,能够在图形化界面里设置拉伸参数。虽然Image Asset有诸如使用方便等优势,可是咱们在使用过程当中仍是须要衡量其带来的便捷性和问题。(1)好比Image Asset会生成1X图片,目前不少应用已经不支持1X的设备了,这样会带来包大小的问题。(2)Image Asset虽然支持矢量图,可是并非运行时的特性,编译后仍然会生成1x 2x 3x图片,对安装包大小没有起到减小的做用,甚至会更大(一个是多了1x图片,另一个是若是本身提供2x 3x图片可在添加前用一些工具用png图片压缩),在图片对安装包大小影响的优化上,一个优化点是能够本身实现对矢量图的支持,这样就提供一份PDF便可,另外一个是使用工具对png图片压缩,还一个比较极端优化的方式是本身在代码里面画图片(能够第一次画而后本地化,以后就直接读取磁盘文件),在具体实施的过程当中,能够借用PaintCode来节省本身编写绘图代码的时间。 (3)还有其Image Asset只支持imageNamed,这样若是一些应用想有本身的图片缓存策略的话就没法实现了。
3.UITraitCollection。 Apple的API文档里面这样介绍UITraitCollection: A trait collection describes the iOS interface environment for your app, including traits such as horizontal and vertical size class, display scale, and user interface idiom. To create an adaptive interface, write code to adjust your app’s layout according to changes in these traits. The iOS trait environment is exposed though the traitCollection property of the UITraitEnvironment protocol. This protocol is adopted by the following classes: UIScreen, UIWindow, UIViewController, UIPresentationController, and UIView. You access specific trait values using the UITraitCollection horizontalSizeClass, verticalSizeClass, displayScale, and userInterfaceIdiom properties. The values that express idiom and size traits are defined in the UIUserInterfaceIdiom and UIUserInterfaceSizeClass enumerations; the value for the display scale trait is expressed as a floating point number. To make your view controllers and views responsive to changes in the iOS interface environment, override the traitCollectionDidChange: method from the trait environment protocol. To customize view controller animations in response to interface environment changes, override the willTransitionToTraitCollection:withTransitionCoordinator: method of the UIContentContainer protocol.
UITraitCollection图例:
UITraitCollection变化流程:
Size Classes。是UITraitCollection里面和Adaptive User Interfaces关系比较大的属性。Size Classes是iOS8以后苹果最新提出来的,和以往的将屏幕以具体尺寸划分不一样,Size Classes对屏幕进行了抽象分类,经过屏幕的感官表现,将其分为普通 (Regular) 和紧密 (Compact) 两个种类 (class)。这样就把屏幕的适配工做从无穷尽的尺寸适配转换成3*3种屏幕的适配,咱们能够在同一种类的屏幕上使用同一种布局,大大减小了适配工做量。之前universal应用使用storyboard时,分红iPad.storyboard 和iPhone.storyboard 来分别写,使用了Size Classes就不须要这么麻烦了。 特别在iOS9中,随着多任务 (multi-tasking) 的推出,这个概念变得愈发重要。
4.Dynamic Type 能够根据Trait Collection的变化调整字体。
5.Layout Guides 参考 The UILayoutGuide class defines a rectangular area that can interact with Auto Layout. Use layout guides to replace the dummy views you may have created to represent inter-view spaces or encapsulation in your user interface. Traditionally, there were a number of Auto Layout techniques that required dummy views. A dummy view is an empty view that does not have any visual elements of its own and serves only to define a rectangular region in the view hierarchy. For example, if you wanted to use constraints to define the size or location of an empty space between views, you needed to use a dummy view to represent that space. If you wanted to center a group of objects, you needed a dummy view to contain those objects. Similarly, dummy views could be used to contain and encapsulate part of your user interface. Dummy views let you break up a large, complex user interface into self-contained, modular chunks. When used properly, they could greatly simplify your Auto Layout constraint logic.
There are a number of costs associated with adding dummy views to your view hierarchy. First, there is the cost of creating and maintaining the view itself. Second, the dummy view is a full member of the view hierarchy, which means that it adds overhead to every task the hierarchy performs. Worst of all, the invisible dummy view can intercept messages that are intended for other views, causing problems that are very difficult to find.
The UILayoutGuide class is designed to perform all the tasks previously performed by dummy views, but to do it in a safer, more efficient manner. Layout guides do not define a new view. They do not participate in the view hierarchy. Instead, they simply define a rectangular region in their owning view’s coordinate system that can interact with Auto Layout.
Layout guides can be used to define an equal spacing between a series of views.
Layout guides can also act as a black box, containing a number of other views and controls. This lets you encapsulate part of your view, breaking your layout into modular chunks.
6.UIAppearance 能够经过appearanceForTraitCollection获取特定UITraitCollection的UIAppearance,而后对UIAppearance进行自定义适配,以达到不一样UITraitCollection自动使用不一样的UIAppearance。
Web View。这块其实并非Native的解决方案,其适配的所须要的技术和策略在web的端早就有不少比较成熟的方案。可是在客户端开发中有必定的应用场景,好比一些运营需求较高的场合,须要作到很强的布局和逻辑的动态化(固然这种状况也可使用Reactive Native、 Weex、luaView甚至JS Patch等动态化方案)。另一种状况是展现类网页内容的app,好比腾讯新闻、每天快报和今日头条等。这些app展现的内容的排版大多从网页而来,若是采用native的方式来解决会十分复杂,所以在大多状况下都使用web view。
代码里判断机型、屏幕尺寸、横竖屏等状态,根据不一样状态并结合必定的缩放策略给UIView设置不一样的frame。这种方式属于比较简单的适配方案,若是项目页面比较多,view的层次比较复杂的话,须要适配的分辨率种类多,代码里会有不少判断的代码,最致命的是若是新增屏幕尺寸须要适配的话,须要很大的工做量。虽然这种方式缺点不少,但也是目前确实国内各大小app使用最多的方案。主要缘由有:
1.项目立项早。早期iOS布局技术较为单一,且屏幕尺寸也不多。因此大部分都是采用手写frame。随着新的屏幕尺寸的增长,因为迁移到新的技术成本太大,因此大多简单适配了事。
2.所需适配分辨率很少。目前大多数应用都是仅支持iPhone版,且除了少数页面大多数页面都是竖屏。因此须要适配的分辨率就有限了。除了320 × 480以外,其余三种屏幕的高宽比基本都在1.78附近。因此通常设计只须要给出一种分辨率的设计稿(通常选择iPhone6--“为何选择iPhone 6做为基准尺寸”或者iPhone5), 只要写frame注意前面讲的一些事项以外,再配合一些简单的缩放策略便可实现适配工做,这是知乎上手机淘宝团队回答的适配和缩放策略。手淘适配规则总结起来就一句话:文字流式,控件弹性,图片等比缩放。控件弹性指的是,navigation、cell、bar 等适配过程当中垂直方向上高度不变;水平方向宽度变化时,经过调整元素间距或元素右对齐的方式实现自适应。这样屏幕越大,在垂直方向上能够显示更多内容,发挥大屏幕的优点。