前言
最近刚刚完成了一个比较大的项目,整体客户端工期是14人天,我投入了10人天,因为服务端已经提前配合安卓上线了,所以整个项目中就是iOS端和测试端的人力投入,这整个项目包括从启动开始到最后渲染包含的东西不少.加之最近也看了好些iOS开发相关的书,我试着把自己学到的一些东西结合实际投入进去.可以感觉到和去年,不,应该是和半年前写代码有一些不同的感触.当然也不是说觉得自己很能写了,而是觉得自己面对一个问题时的思路比以往多了一些.主要表现在当觉得某一个方式实现功能不太方便时,我会尝试其他一些方式.习惯在实现一个复杂功能的时候先列出流程点.工程启动较慢,我就使用自己本地的小demo对一些不是完全把握的点先进行测试.遇到app表现和预期不一致时,不在直接打断点寻找问题,而是先思考一下可能的点,在追代码,然后断点.但是还是有很多需要加强的地方,架构设计这一块还是很模糊,很想把数据和视图隔离开来,总是差点意思.这个还是需要继续加强的. 更明显的点是因为前期把功能点分的比较细致,到后面相互模块间交互时,写起代码来就感觉比以往轻松了,很容易实现模块的相互交互.
总结
下面是对整个开发流程中遇到的一些问题的解决方案和个人的一些总结.方便自己记忆和后续再次遇到时能尽快解决.基本都是一些开发中很基本的问题,而且大多数在看书时也看到过,但还是忘记了在开发时要避免.
1、页面粒度尽量细化
在以往的开发中,我也会细化页面粒度,但是经常在开发中会有一个想法, 当两个视图从某些角度来看比较类似时,我就喜欢使用一个视图的不同状态来呈现.然后根据状态的不同来将子视图进行隐藏或者高度置为0.这样做很明显一个坏处就是,无论是数据源还是视图,总是需要一些个变量来操作当前需要什么数据,需要展示什么样式.带来了另一种程度上的臃肿.不过现在来看,感觉这也违背了单一职责的原则.视图虽然不应该跟交互绑定,但是一个视图对应的事项应该尽量少,这样才可以降低类与类之间的依赖.但是也需要根据自身情况考虑一些其他问题,有时候UI的复用如果带来的收益大于重新写一套带来的收益,我认为还是尽量复用原有组件比较好.经常也会有一些组件只需要稍微改动其中一小点的样式就可以达到本次需求的效果(这在我当前的公司还是比较常见的).我一般会给产品和设计的同学给出两种方案.1、尽量按照公司原本样式推进,不能因为一条业务线就使用一种新的样式,因为这样带来的结果是没条业务线都想要一些自己独特的风格.2、如果无法说服产品,当然标准还是需要推行的,我们会扩展原本的样式,相当于提供一个新的能力.但是也会要求以后需要按照新的标准来实行,同时推动产品侧同步公司的一套样式标准.
2、尽量将VC瘦身
本次开发中,我尽量把代码放到视图容器中,在VC(viewcontroller)通过申明一个视图容器来持有视图容器,最后的结果是VC中的代码没有超过100行.使用这个做法是之前有看到过一篇讲SDWebImage的博文中说过:“把复杂的东西留给自己,把简介的外表留给他人”.因此我把很多的数据交互逻辑放到了上面提到的视图容器中.视图容器不进行UI相关的操作,但是会持有子视图,自视图也正是需求所需要的U.视图容器中还会有数据逻辑的变化和交互处理,以及一些通知和代理的处理逻辑.当然这部分应该是可以在通过MVVM/MVP的设计思想在进一步优化一下的,但是当时做的时候我个人感觉对于这样的设计模式还是有一些模糊,简单来说就是知道这两种模式的意思,但是自己要按照这样实现起来还是有一定难度.大部分时间我都认为coding是一个模仿的过程,因为我们app内大多数都是以MVC为主,或者进行了一些变化.这也是我一段时间内都习惯于这种模式的编码,如今,在觉得某些场景不适合的情况下,我也会尽量使用新的模式或者通过将代码结构粒度细化的方式来实现需求.
3、弹出新页面
中间遇到一个需求点,要求点击视图中一个按钮,弹出一个新的视图覆盖在表面.这个问题解决方案比较多,首先是半透明的背景,然后在背景中增加一个需要展示的视图.因为是在首页,底部是存在一个tab,所以直接使用普通视图是无法覆盖底部tab的.可以选择1、将要展示的视图使用window承载,设置window背景色为半透明.2、将要展示的视图使用viewcontroller承载,设置vc的view背景色半透明,使用模态弹出的方式弹出视图.我考虑使用现成的模态弹出动画,因此我使用一个VC来承载弹出的视图.这样做也一定程度上分离了功能点,最后我只是在视图容器中声明了需要弹出的VC,就达到了最后需要的效果.
4、数据传递
本次开发中另一个让我觉得棘手的问题是数据的传递,因为页面粒度的细化,层级加深.带来的问题是外面的数据要传递到内部需要一层一层传递,内部的事件想要外部响应,也需要一层层往外传递.当这个时候我很不愿意给视图类增加一个fetch的public方法来提供给外部取数据向内传递,我原本想通过内外都增加一个共有的第三方类来处理信息和事件的内外流动,但是设计过程中效果甚微,导致短期看不到结果,为了不拖延工期,我还是使用数据一层层传递的方式,然后在中间增加了一些代理和block来处理事件的调用.对于这一点在后续codereview时我也会向相关同学请教一下更好的处理方式,在某个合适的时机在进一步实践.搞好这一点对于架构设计应该会更明朗一些.
5、圆角和阴影共存
这个问题应该来说遇到好多次了.这次mark一下,之前都是遇到找一下相关博客,这次尽量记住.参考博客圆角阴影共存,总结下来需要注意以下几点1、设置了layer.masksToBounds = YES,将导致阴影失效,因为这个属性会将超过视图的部分截断.2、如果对一个view的一个layer同时设置圆角和阴影,那么两者是无法并存的,因为设置圆角时我们总会将masksToBounds设置为yes保证圆角生效.因此参考博客后我的做法是使用两个view,外部的一个view设置阴影,内部的一个view设置圆角,同时设置外部view的backgroundColor为clear.另外在iOS 11之后提供了给视图指定位置设置圆角的方法.
1 | /* maskedCorners是一个枚举值, |
6、masonry动画
iOS中提供了非常成熟的视图动画效果,在我们app内可以应对绝大多数的日常需求开发.但是以往我使用的动画都是通过修改view的frame布局实现的,本次因为使用masonry来布局,实现动画时通过mas_updateConstraint,但是最后没有得到的动画效果.查找一些资料后发现需要在动画过程中强制出发UI的重新渲染,masonry动画,mark一下,方便之后使用.
1 | // 这句代码需要在UIView,animation动画过程中添加 |
7、属性修饰符
这次开发中还有一个非常低级的错误,以下是背景:保存下拉刷新时的列表数据源,在上拉加载更多时,将新的数据源追加到保存的数据源的末尾,再将新的数据返回给外部进行渲染.我将保存的数据源的变量使用如下修饰:
1 | ///此处的修饰符应该使用strong而不是copy |
就是这个很小的错误我debug了很久,就觉得很奇怪,为什么一个ArrayM类型,会被识别为ArrayI类型.然后在我追加数据的方法里面carsh
1 | - (void)addObjectsFromArray:(NSArray<ObjectType> *)otherArray; |
好在最后找到了原因,使用copy修饰可变类型,无论如何初始化,最终也会在内存中被copy成一份不可变的类型.这是copy这个修饰符的特性.不会增加成员的引用次数,而是copy一份副本.strong修饰是增加成员的引用次数.对象不会拷贝一份新的.这也就是根本原因.虽然这一点我之前在书上也是看到的,但是不小心还是出错了….
然后随便讲下mutableCopy和copy方法.这两个方法是定义在NSOBJect中的,对于可变类型,使用mutableCopy得到的结果还是可变类型,使用copy得到的就是不可变类型,对于不可变类型,使用mutableCopy得到的结果是可变类型,使用copy得到的还是不可变类型.
另外,对于不可变类型我们一般使用copy修饰符修饰,因为copy会对属性增加一份拷贝,如果使用strong的话,因为赋值前后两者都指向同一份地址,容易导致修改原数据时也不经意修改了赋值后的数据.
8、开发中的小tips
开发过程中经常会对需要做到的一个效果保持怀疑态度,不确定这个方案是否正确,我一般的做法是本地写一个简单的demo,然后在demo中实现我需要的方案的简化板.如果成功,说明这是我期望的效果.不成功的话,就需要进一步在探索了.这个做法可以节约不少时间,因为我们app的主工程因为历史原因,代码量超大, 每次编译需要的时间比较长,使用小demo可以节约不少时间.
9、导航栏黑线隐藏
1 | [self.navigationController.navigationBar setShadowImage:[UIImage new]]; |
注意,此设置后全局的navigtionBar的导航栏黑线都会消失,如果只是对某一场景定制的话,可以使用成员属性保存ShadowImage和BackgroundImage,然后在viewdiddisappear的方法里面在设置回来.(使用者方法是因为我们app内没有对导航栏黑线特别在意,基础库中没有提供相应方法.)