几个月前完成了MVVM/RAC的学习,最近一直在默默重构项目代码,写了很多代码,过了一段时间发现自己的代码风格还是有的代码质量大大提高。前几年在一家小公司负责iOS客户端,后来负责客户端的开发。我被乱七八糟的东西分心了,所以到去年,我没有写太多的代码。到新公司半年多了,目前只是写代码的一个小角色,所以我的精力基本都在写业务代码,业余时间学习乱七八糟的技术。在过去的一个月里,除了花时间和精力在重构上,还有一些模块需要增加功能。由于项目中有很多代码历史因素,首先要做的往往是对代码进行重构和整理,我发现有很多地方是我之前写代码的时候没有注意的,比如乱用全局变量;方法没有层次感,胡乱添加;在业务不了解的情况下,通过打补丁等方式实现功能。所以我决定写一篇文章,总结分享一些我认为在实践中需要注意的事项。减少对象属性是提高代码质量最简单的方法。很多代码乍一看很乱。里面定义了几十个不同的对象变量,不同逻辑之间莫名其妙。分离。一是定义方式不对。很多莫名其妙的内部变量暴露在头文件中,让外部调用者不知道public可以操作哪些方法。另外,其实根据我自己这段时间的重构经验,大部分都可以用局部变量或者__block变量代替。1.尽量少在头文件中暴露变量或方法,但是用extension或category放在.m文件中,或者在专门的私有头文件中头文件中暴露的信息越少越好,全无必要信息应避免在暴露的m文件的扩展中,定义conforms协议和对象属性,使用getter/setter定义对象属性。2.使用局部变量或__block变量代替局部变量。不用说,当你需要写代码的时候,你需要有更清晰的头脑。写完了,即使commit之前review了,也要check一下。您对自己的代码质量负责。代码审查经常检查Noredundantorobsoletecode。不要添加多余的对象属性,不要留下注释掉的代码,不要留下无用的代码。这些都是基本功,但是很多开发者就是做不到,或者不爱写代码,那么多废弃的代码,我重构代码的时候,虽然业务不熟,但是大部分模块能删掉十分之一的代码和大量的对象属性。这根本就不够用心。关于__block变量的使用,这是我在Android开发中感觉最不满意的地方。这个功能简直太酷了。比如这里,在使用block的时候,返回了一些变量。例如这里,我需要记录平移手势开始时headerView的顶部坐标。结合RAC后,需要全局变量记录的值可以使用__block变量来完成。.您可以尽可能避免循环引用。许多开发人员会忽略一个地方。在block中使用_XXX对象变量时,block会保留self指针,一不小心就会造成循环引用。所以如果使用局部变量,就可以把这种问题扼杀在摇篮里。减少和模块化对象消息1.减少对象消息,减少UI动作消息。感谢blockandRAC,或者说blockskit,我们可以使用hooks来用blocks替换之前的target-action模型。UI和action的代码终于可以一起使用了,让整个逻辑变得紧凑,看代码终于不用再跳来跳去了。另外,在日常开发中,尽量使用block,而不是写各种协议或者传递target/selector。相信我,这将使代码更易于阅读。2.模块化使用“#pragmamark-XXX”来划分不同逻辑之间的界限,使得整个文件阅读起来更有条理。另外一个我现在用的最多的就是设置Xcode的快捷键,把快捷键Ctrl+6改成显示文档结构:Command+J,搜索快速跳转到对应的message和module,尽量避免文档结构显示多于两屏,多于两屏有点多,必须考虑重构。我通常划分的模块有:生命周期、uihelper、datasource/delegate、按功能划分的模块等下面是我最近重构的一个ViewController的文档结构MVVM&&RAC自己使用MVVM思想的感受太爽了.我告诉你,MVVM不一定要用到RAC,但是databinding是少不了的。在iOS中,它是KVO。我建议大家尝试一下。感觉这基本就是MVVM的核心了。连AndroidSDK也不得不引入这个特性。将数据部分的逻辑提取放在ViewModel中,然后让UI和ViewModel中的数据进行绑定。这样不会减少代码量,但是绝对可以大大简化开发时的逻辑复杂度,而且不需要重写-setXXX:方法来更新很多不相关的UI。关于UI开发,后面再说新的。我在这里说说我自己的理解。有人说RAC影响性能,回调栈太深。确实如此,但是个人感觉RACObserver是基于KVO实现的,调用的时候是同步调用的,所以对性能的影响有限,而且调用顺序不会有问题,所以才敢在列表开发中使用数据绑定。经过实践,还可以,对用户体验没有影响。关于RAC,即使你不使用RAC,也有一些东西绝对值得在你的项目中引入,比如@weakify(self)/@strongify(self)。如果通过预编译查看,这个方法是设置一个局部变量self来覆盖全局self,从而避免循环引用,需要注意的是block层级较深时的问题,http://stackoverflow。com/questions/21716982/explanation-of-how-weakify-and-strongify-work-in-reactivecocoa-libextobjc。RAC/MVVM,刚开始学习的时候写了两篇文章,也算是自己的总结吧。本人明白以上仍有不足之处。请参考:http://blog.csdn.net/colorapp/article/details/46524893、http://blog.csdn.net/colorapp/article/details/46537729。大家可以通过我博客文章的参考链接学习。UI开发1.重写setter方法和CodeBlockEvaluationCExtension语法重写UI的getter方法,将UI的初始化放在getter中,减少-viewDidLoad的负载,同时使整个页面清除;同时,可以通过UseGCCCodeBlockEvaluationCExtension({...})语法来构造局部变量初始化和处理的逻辑。这个语法可以参考我之前的博客:http://blog.csdn.net/colorapp/article/details/47006771。关于setter代码风格,可以参考别人写的一篇文章,http://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-一个。html,在我们Q群讨论过这个问题后,我也非常认同这种UI的写法。举个例子,在-viewDidLoad中,作为逻辑入口,代码会更少但更清晰,代码如下:然后重写bgView的getter方法,包括View和frame,可以使用({...})syntax代码结构和层级:2.复杂UI的开发有时候我们在做业务的时候,产品需求往往很复杂,炫酷的UI加上各种综合逻辑,这样的结果就是,coder超长的代码,而我们在日常工作中遇到的问题,大部分都是这样的问题。关于这个问题,我的解决方案是结合UI/自定义视图/子视图控制器来解决。(1)combinedview的概念是借鉴了Android。重构的时候看了项目中的代码,发现大家在用它做UI的时候,对这个概念的理解不是很深。感觉他们对UIView的视图层级理解不够。例如,在一个复杂的UI中,所有的子视图都直接堆叠在父视图之上。结果,很难调整子视图的框架。我个人的做法是先把复杂的UI分成块,从左到右或者从上到下,把每个UI元素放在不同的容器视图上,然后把这些容器视图组合到超视图之上,这样有利于很明显,首先UI干净明了,读起来不那么费劲。二是你计算坐标或设置约束变得非常简单,因为当你调整一个UI元素时,你只需要考虑它和包含它的容器视图之间的坐标关系,而不用经过大量的无聊的计算和最外层的superview相关联。另外,可以充分利用AutoLayout、autoresiziingmask等UI工具,使用起来会非常方便。更重要的是,结合RACObserver这个强大的工具后,可以很方便的根据数据更新ui。比如我前段时间在我们的项目中重构的一个页面。这个首页列表对性能要求比较高。它不是使用AutoLayout实现的,但不使用AutoLayout并不是不干净地编写它的理由。这是我对UITableViewCell的分层。最外层由图标视图/右视图/底视图等容器视图组成,右视图的容器视图由子右顶视图/右中视图/右底视图等容器视图组合而成,具体的UI元素被放置在这些子容器视图中。这样,UI代码就会以层次化的方式展现出来。init/layoutsubviews只需要维护self和containerview的关系,具体显示数据的UI元素只和subcontainerview有坐标关系。我们来看一下右视图的容器视图的代码实现:关于性能,感谢iOS,我们在Android中没有页面层级深的时候性能卡顿的问题,放心UI是分层。(2)自定义视图非常复杂且相对Standalone或可复用UI,适时使用自定义视图子类化。对于UI的简单展示,我们只需要简单的使用组合视图就可以实现。但是有时候,我们会遇到一些包含复杂逻辑的情况,无论是动画。这时使用组合View来实现,一方面容易混淆逻辑,会使文件的文档结构变得非常复杂。简单换句话说,对象有很多消息。这时候我们可以通过自定义视图来实现。其实这也是一个组合视图,只是我们只是把这些组合视图变成了一个类,只暴露了少量的接口给外部调用。如果这个自定义视图会出现在多个业务模块中,就需要用一个单独的文件来容纳这个类。如果只是本模块使用,可以直接写在本业务模块的文件中。没有必要所有的类都有一个文件,我们就把它当作这个“内部类”。什么时候用自定义视图而不是组合视图,我想了很久。当你觉得组合视图的代码比较乱时,不客气,打包成自定义视图即可。最近遇到的几个问题是在用UICollectionView做部分UI的时候,还有很多其他的UI元素,我就自己写一个自定义view。比如下面这个文件中,一个左右滑动查看图片的UI,封装了一个自定义视图PhotoView,内部使用了UICollectionView,实现了一个相对独立的模块。这个时候其实可以把这个控件封装成一个相对独立的模块。类我觉得比较合适。(3)containerviewcontroller的用法对于很多开发者来说比较陌生或者用得不多,但是在实际业务中,这项技术非常有用,可以大大提高开发效率。如果对这部分知识不熟悉,可以参考我之前的博客:http://blog.csdn.net/colorapp/article/details/45765601。对于业务逻辑和生命周期要求相对独立的业务,使用childviewcontroller进行封装。如果parentviewcontroller和childviewcontroller的关系很近,使用ViewModel和block连接parentviewcontroller和childviewcontroller。使用子视图控制器开发UI而不是自定义视图有很多优点。我个人认为最大的好处是可以方便的使用ViewController和ViewControllerHierarchy的生命周期,比如在-viewWillAppear/-viewDidDisappear中做一些操作,又比如直接获取UINavigationController指针等等。之前的做法一般是在ViewController对应的生命周期中调用自定义view的方法,将self.navigationController指针传递给自定义view等。这样你不仅可以把UI相关的代码打包到这个子viewcontroller中,同时也将网络请求和数据处理逻辑放到子视图控制器中,这样就可以避免出现每次出现超过1k行的视图控制器。在使用MVVM之后,还有一个有益的用法。比如在共享一些数据的时候,我们之前都是来回传递对象。这种问题很容易引起混淆。这时候,我们可以通过传递ViewModel来避免这个问题,ViewModel既负责网络请求,也负责数据处理,而父视图控制器和子视图控制器需要做的就是与ViewModel进行绑定。AutoLayout/Masonry在一些没有这么强性能需求的非列表页面,我们可以大量使用AutoLayout来开发UI,充分利用UI基于数据的自适应能力,甚至不需要在容器视图中调整UI。有一段时间,我根本不想开发iOS。原因很简单。Android的布局和可见的开发方式非常方便。再加上AS这样的神器,感觉效率不比iOS低。我是在项目最新支持改为iOS6后才开始使用AutoLayout的。虽然比较费力,但是我觉得这对UI开发来说是一种解脱。至于Masonry这个框架,之前对它有些疑惑,不敢用,于是看了源码,发现封装的很精巧,很多设计思想也很值得学习。对源码感兴趣的可以参考我的Blog:http://blog.csdn.net/colorapp/article/details/45030163。看完源码后,我尝试用Mansory开发了一个显示信息的页面,感觉非常爽!这样做的好处是,设置好UI数据后,就不用想着更新UI了,世界瞬间干净了。...,下面是我的一个简单的例子。结合({….})语法和RAC,可以使用最简单的命名如label来为UI设置数据。这对于我们开发UI来说绝对是一种解脱。先说下AutoLayout的问题:1.第一个问题,如果一个view不是leafview,那么如果隐藏了UIView,它的constraints还在起作用,所以会留一个空白,不会像在Android中一样设置。GONE太方便了。国内的sunny大神开源了一个很好的解决方案,https://github.com/forkingdog/UIView-FDCollapsibleConstraints。这里说一下我之前的解决方案,比较粗糙,直接子类化:2.动画的问题UsingAutoLayout动画问题比较大。通过改变约束来制作动画一直是我头疼的问题。所以一般遇到这类问题,我尽量避免使用AutoLayout来解决,而是使用frame来解决。可以参考objc.io上的一篇文章:http://www.objc.io/issues/3-views/advanced-auto-layout-toolbox/。3、多行UILabel的问题在iOS7及以下操作系统上,UILabel不足以显示多行文本。您需要将UILabel的preferredMaxLayoutWidth设置为固定值才能显示多行文本。iOS8以后就不用设置了。4.UIScrollView问题和constraintambiguity等问题参考我的文章:http://blog.csdn.net/colorapp/article/details/47007143这个地方,我的建议是根据具体问题选择实现方式:spring&不管是structs还是AutoLayout,用能更简单快速解决问题的那个。它不必固定在一个行为中,尤其是当开发的页面有很多动画时。注释里不要写一堆中文注释,代码里也不要有很多中文。OC已经够啰嗦了,代码就别写这么啰嗦了。业务代码除了提供服务的公共函数或方法外,只能在一些关键点进行注释。它不需要很多中文,这太低了。代码可以自行注释。如果需要评论,可以使用秒神的Xcode插件来实现,https://github.com/onevcat/VVDocumenter-Xcode。至于那些有拼音起名码的,如果能做主,就毫不犹豫的打开吧。在这里吐槽一下,之前公司有这样的哥们,我没招,老板硬塞给了我。善用OC的新语法OC有很多新的语法糖,可以大大提高我们的效率,参考苹果指南:https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html。比如打印数字的时候我们可以使用@(xxx)来打印,定义枚举的时候使用typedefNS_ENUM,使用instancetype代替id等等。最近又到了每年刷新技能槽的时候了。iOS9发布,OC有一些新的语法。让我们学习并更多地使用它。JSON数据处理的新手往往对此有些困惑。比如服务器返回的数据格式不正确,包括null,很容易导致项目崩溃。这个问题可以使用Mantle来解决。很多兄弟都在用这个,但是我自己没用过。之前写了一个小框架放在了github上,https://github.com/lihei12345/CYJSONSValidator,我们的项目也用到了,效果不错。用于解析数据时,会检查数据的类型、是否为Null等,以保证解析出的数据类型的正确性。当key可能不存在时,也可以设置一些默认值。例如:块使用块而不是委托。对此无话可说。它使代码非常紧凑,并减少了文件中的消息数量。最重要的是,关系还没有那么亲密。对于大量的委托方法,可以考虑使用协议实现。这时候块太多也会影响阅读。同时,对于传递target/selector,尽量使用block。这种阅读太不方便找了。及时将代码提交到stage是非常重要的。在开发过程中,经常需要对比上一步的代码,从而最大程度的保证你的修改是正确的。如果有一些小问题,你甚至可以找到历史版本。及时commit,每完成一个比较完整的需求commit。小提交是一个好习惯。做好PR代码审查需要花费大量时间。如果可能的话,最好每个版本都召开一次总结会。RAC封装网络请求返回的信号应该避免多重副作用,但是不应该使用replay/replayLazily,因为不会调用dispose。使用RACCommand封装请求,查看这些文章:http://codeblog.shape.dk/blog/2013/12/05/reactivecocoa-essentials-understanding-and-using-raccommand/,https://github.com/ReactiveCocoa/ReactiveCocoa/issues/963,https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1326。结合RACCommand和takeUntil:来封装一个可取消的请求。
