iOS代码实践总结
最近一个月除了专门抽时间和精力重构之外,还有就是遇到需要添加功能的模块的时候,由于项目中的代码历史因素比较多,第一件干的事情往往是重构整理代码,发现很多之前的代码写的时候没有注意的事情特别多,比如全局变量乱用;方法没有层次感,胡乱添加;对业务不了解的情况下,通过打补丁的方式实现功能等等。
AD:
前几个月完成对MVVM/RAC的学习之后,最近一直在默默地对项目代码进行重构,写码比较多,过了一段时间回头发现自己的代码风格还有代码质量都有大大的改善。过去几年在一家小公司负责iOS客户端后来负责客户端的研发工作,被杂乱的事情分神比较多,所以到去年的时候,写码已经不太多了。在新公司待了大半年,目前只是写码的小角色,所以精力基本上在写业务代码和业余学习乱七八糟的技术上面。
最近一个月除了专门抽时间和精力重构之外,还有就是遇到需要添加功能的模块的时候,由于项目中的代码历史因素比较多,第一件干的事情往往是重构整理代码,发现很多之前的代码写的时候没有注意的事情特别多,比如全局变量乱用;方法没有层次感,胡乱添加;对业务不了解的情况下,通过打补丁的方式实现功能等等。所以我决定写一篇文章,把自己的觉得实践中需要注意的一些事项,具体总结一下分享给大家。
减少对象属性
这个是最容易改善代码质量的一个点,很多代码一眼看上去就会让人感觉很凌乱,一上来就是几十个不同的对象变量定义在里面,这让不同逻辑之间莫名其妙没法分开。一个是定义的方式不对,很多莫名其妙的内部变量暴露在头文件中,让外部调用者根本不知道哪些才是public可以操作的方法。另外实际上,经过我自己这段时间的重构经验来看,大多数是可以通过局部变量或者__block变量来代替的。
1. 头文件中尽可能少暴露变量或方法,而要使用extension或者category放在.m文件,或者专门的private头文件中
头文件中暴露的信息越少越好,一切不必要的信息都不要暴露出来
m文件的extension中,定义conforms protocol和对象属性,对于对象属性的定义,使用getter/setter 来定义。
2. 使用局部变量或者__block变量代替
局部变量不需要多说,需要写码的时候思路清晰一些,写完之后在commit之前即使review一定要check一遍,对自己的代码质量负责,code review往往检查不出来冗余或者废弃的代码。不添加一个多余的对象属性,不留注释掉的代码,不留没有用途的代码,这些都是基本功,但是很多开发者就是做不到,或者说对写码没有爱,所以很多废弃的代码,我重构代码的时候,虽然对业务不熟悉,但是大多数模块都能删除掉十分之一的代码和大量的对象属性,这个是单纯的不够用心。
关于使用__block变量,这个是Android开发中我感觉到最不满意的地方,这个特性简直太他妈爽了。
比如这里,使用block的时候回传一些变量
再比如这里,我需要记录一个pan手势开始时,headerView的顶部坐标,结合RAC之后,本来需要全局变量来记录的值,使用__block变量即可搞定
3. 可以尽可能避免循环引用
有个地方很多开发者会疏漏,在block中使用_XXX对象变量的时候,block会retain self指针,一不小心就会造成循环引用的出现。所以使用局部变量的话,就能扼杀这种问题在摇篮之中。
减少和模块化对象消息
1. 减少对象消息
减少UI的action类消息,感谢block和RAC,或者blockskit,让我们得以通过hook来把之前target-action模型换为block来实现,UI和action的代码终于可以一起了,使整个逻辑变得紧凑,在查看代码的时候终于不用跳来跳去了。还有就是日常开发中,把自己写的各种protocol或者传递target/selector的地方,尽量使用block来代替,相信我,这个会使代码好读很多。
2. 模块化
使用”#pragma mark - XXX”进行分割不同逻辑之间的界限,让整个文件阅读起来更加结构化。还有一个我现在最常用的就是是设置Xcode的快捷键,把Ctrl + 6 显示文档结构的快捷键改为:Command + J ,搜索来快速跳转到对应的消息和模块,要尽量避免文档结构显示超过两屏幕,超过两屏幕说明有点多了,你肯定考虑一下重构了。
我个人习惯一般划分的模块有: life cycle,ui helper,datasource/delegate,依据功能进行划分的模块等等,如下是我最近重构的一个ViewController的文档结构
MVVM && RAC
我自己使用MVVM思路的感觉是太爽了,说一下,MVVM不一定需要使用RAC,但是data binding少不了,在iOS中也就是KVO了,建议大家都去尝试一下,我自己感觉这个基本上MVVM的最核心的东西了,连Android SDK也不得不引入这个特性。把数据部分的逻辑抽取放在ViewModel中,然后让UI和ViewModel中的数据binding,这个不会减少代码量,但是绝对可以大大简化开发时逻辑的复度,再也不用重写-setXXX:方法来update一大堆不相关的UI了,关于UI开发,后面会专门再讲讲新的。这里说一下我自己的理解,有人说RAC影响性能,回调栈太深,这个的确是会有的,但是个人感觉RACObserver是基于KVO实现的,调用的时候是同步调用的,所以对性能的影响有限,也不会出现调用顺序的问题,所以我敢在列表开发中使用data binding,实践之后还好,对用户体验没什么影响。
关于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方法和Code Block Evaluation C Extension语法
重写UI的getter方法,把UI的初始化放在getter中,减轻 -viewDidLoad的负荷,同时可以使整个页面变得清晰;同时,可以通过使用使用GCC Code Block Evaluation C Extension ({…})语法,结构化局部变量初始化和处理的逻辑。关于这个语法,参考我之前的博客: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-an.html,这个问题之前在我们Q群里探讨之后我也非常认同这种方式写UI。
举一个例子,-viewDidLoad中,做为逻辑的入口,代码会变少但是变清晰,代码如下:
然后重写bgView的getter方法,包括View和frame这些都可以使用({...})语法使代码结构化层次化:
2. 复杂UI的开发
有时候我们开发业务的时候,产品需求往往非常复杂,酷炫的UI加上各种考虑全面的逻辑,这个的结果就是,码农的超长代码,而我们平时工作面对的也大多数都是这类问题。关于这个问题,我的解决方式,组合式UI / custom view / child view controller来解决。
(1) 组合式view
这个概念是从Android中借鉴而来。重构时查看项目中的代码,发现大家用的做UI的时候,对这个概念不是很强烈,感觉是对UIView的view hierarchy理解不够。比如一个复杂的UI,直接把所有的subviews直接堆积到super view上面,这样的结果就是,调整subview的frame非常困难。我个人的做法是,首先对复杂UI进行分块,从左到右或者从上倒下,把各个UI元素放到不同的container view上面,然后组合这些container view放到super view上面,这样的好处非常明显,首先UI干净清晰,阅读起来不那么费劲。其次就是你计算坐标或者设置约束会变得很简单,因为你调整一个UI元素的时候,只需要考虑它与包含它的container view的坐标关系即可,而不是通过一大堆无趣计算跟最外层super view关联起来。还有就是可以充分利用Auto Layout和autoresiziingmask这些UI利器,使用的时候会非常方便。再有就是结合RACObserver这个利器之后,你能很容易做到根据data来update ui。
举个例子,是我们项目中前一段时间我重构的一个页面,这个首页列表,性能要求比较高。并没有使用Auto Layout来实现,但是不使用Auto Layout并不是不把它写的很干净的理由。
这是我对一个UITableViewCell的分层,最外层由 icon view / right view / bottom view这些container view组成,而right view这个container view则又是由right top view / right middle view /right bottom view 这些 sub container view组合而成,而具体的UI元素则是放在这些sub container view之中。这样UI代码就会以一种层次化样式展示出来,init/layoutsubviews只需要维护self与container view的关系即可,而具体展示数据的UI元素也只跟sub container view存在坐标关系。我们看一下right view这个container view的代码实现:
关于性能的话,感谢iOS,我们不存在Android中页面层次较深性能卡顿的问题,放心把UI层次化就行
(2) custom view
对于非常复杂并且相对独立或者可以重用的UI,及时使用custom view子类化。对于单纯的展示UI,我们只需要简单通过组合式view就可以实现了。但是有时候,我们会遇到一些包含无论是动画,逻辑都比较复杂的情况,这个时候使用组合式View去实现,一方面容易把逻辑弄混乱,会把文件的文档结构变得很复杂,简单来说就是对象的消息数量很多。这个时候,我们可以通过custom view来实现,实际上这个也是组合式view,但是我们是把这些组合式view变成了一个类而已,只暴露少量的接口给外部调用。如果这个custom view会出现在多个业务模块中,那么有必要使用一个单独的文件来容纳这个类,如果仅仅是这个模块一个使用的话,可以直接写在这个业务模块的文件中即可,没有必要对所有的类都单独一个文件,我们就当作这个“内部类”来弄了。
什么时候使用custom view而不是组合view,我想了很久,你觉得组合式view的代码很乱的时候,别客气,包装为一个custom view就行了。我这边最近遇到的几个问题是使用UICollectionView来做部分UI的时候,同时还有其他很多UI元素,我会写一个custom view。比如下面这个文件,把一个左右滑动查看图片的UI使用PhotoView这个custom view进行包装,内部使用UICollectionView实现一部分相对独立的模块,这个时候这个控件实际上是可以包装为一个相对独立的模块的,用子类我感觉比较合适一些。
(3) container view controller
这个用法很多开发者不熟悉或者说是用的不多,但实际业务中,这个技术非常有用途,可以大大提高开发效率。对这部分知识不熟悉的,可以参考我之前的博客:http://blog.csdn.net/colorapp/article/details/45765601。对于有相对独立业务逻辑以及生命周期要求的业务,使用child view controller进行包装,如果parent view contrller与child view controller之间非常密切,则使用View Model以及block来对parent view controller和 child view controller 进行衔接。
使用child view controller来开发UI而不是custom view的优势很多,我个人认为最大优势在于可以方便利用View Controller的生命周期以及View Controller Hierarchy,比如在-viewWillAppear/-viewDidDisappear中做一些操作,再比如直接获取UINavigationController指针等等。之前的做法一般是在View Controller的对应生命周期内调用custom view的方法,传递self.navigationController指针给custom view等。所以可以不仅仅把UI相关的代码包装进入这个child view controller,也可以把网络请求,数据处理这些这些逻辑放到child view controller中,这样下来就能避免那种动不动超过1k行的view controller的出现了。
利用MVVM之后,还有一个比较有好处的用法,比如公用一些数据的时候,之前我们是把对象传递来传递去,这样的问题是很容易出现混乱,这个时候我们是传递ViewModel就可以避免这个问题,ViewModel既负责网络请求又负责数据处理,而parent view controller与child view controller所需要做的事情就是跟ViewModel进行binding而已。
Auto Layout/Masonry
在一些性能要求不是那么强烈的非列表页,我们可以大量使用Auto Layout来开发UI,充分利用UI根据数据的自适应能力,连在container view中调整UI的步骤都不需要了。之前有一段时间我根本不想开发iOS,原因很简单,Android的布局式以及可见式的开发方式非常方便,再加上AS这样的神器,我自己感觉效率不比iOS低。自从项目最低支持变到iOS6之后,我才开始使用Auto Layout,虽然比较费劲,但是感觉这个对UI开发来说是个解脱。
至于Masonry这个框架,之前我对这个抱有一定的怀疑不敢使用,所以我把源码读了一遍,发现这个包装很薄很巧妙,很多设计思路也值得借鉴,对源码有兴趣的可以参考我的博客:http://blog.csdn.net/colorapp/article/details/45030163。我读完源码之后,尝试着完全使用Mansory来开发一个展示信息的页面,感觉太爽了!
这个的优势就是你设置UI的数据之后,不需要再考虑去update ui了,这样世界瞬时就清净了。。。。,下面是我一个简单的示例,结合({….})语法和RAC,可以使用最简单的label这样的命名来对UI设置数据,这个对我们开发UI来说,绝对是一种解脱。
说一下Auto Layout的问题:
1. 首先一个问题,是如果一个view不是leaf view的话,那么这个UIView如果hidden的话,它的约束仍然是work的,所以会留下空白,不会像Android中那样设置GONE那么方便。国内sunny大神开源一个不错的解决方式,https://github.com/forkingdog/UIView-FDCollapsibleConstraints。这里说一下我之前的解决方式,比较土逼,直接子类化:
2. 动画的问题
使用Auto Layout有一个比较大的问题在于动画,通过更改约束来进行动画,一直是我比较头疼的问题,所以一般遇到这类问题的时候,我都会尽量避免使用Auto Layout来解决,而是使用frame的方式来做。可以参考objc.io上面的一篇文章:http://www.objc.io/issues/3-views/advanced-auto-layout-toolbox/。
3. 多行UILabel的问题
iOS7以及以下的操作系统上,UILabel显示多行文本是又不足的,你需要设置UILabel的preferredMaxLayoutWidth为一个固定值才能显示多行文本。在iOS8以后就不再需要设置这个了。
4. UIScrollView的问题以及约束歧义和其他问题
参考我的文章:http://blog.csdn.net/colorapp/article/details/47007143
这个地方,我的建议是根据具体问题来选择实现方式 :spring & structs也好,Auto Layout也好,那种解决问题较为简洁快速就用那种,不一定非要固定于一种行为,尤其是开发的页面有大量动画的时候。
注释
不要写一堆中文注释,代码不要出现大量的中文,OC已经够啰嗦,不要这么啰嗦地写码。除了提供服务的public功能或者方法,业务代码仅在某些关键点上注释一下就行,不需要一大堆中文,这样太low,代码自注释即可,需要注释的,可以通过喵神的Xcode插件来实现,https://github.com/onevcat/VVDocumenter-Xcode。
而对于出现拼音命名代码的人,能做主的话,别犹豫,开掉吧。这里吐一下槽,之前的公司就有这样的哥们,不是我招进来的,老板硬塞给我的。
善用OC的新语法
OC有很多新的语法糖,可以大大提高我们的效率,参考Apple Guide:https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html。
比如打印数字的时候,我们可以用@(xxx)来打印,定义枚举的时候使用typedef NS_ENUM,使用instancetype而不用id等等。而最近又到了每年技能槽刷新的日子了,iOS 9发布了,OC又有了一些新语法,去学习一下多用用吧。
JSON数据的处理
新手往往会被这个稍微困惑一下,比如服务器返回的数据格式不正确啦,包含null啦,都很容易引起项目崩溃。这个问题可以使用Mantle来解决,很多兄弟都在使用这个,我自己倒是一直没有用过。之前写了一个小框架放在了github上面,https://github.com/lihei12345/CYJSONValidator,这个在我们项目内部也在使用,效果不错,用来解析数据的时候,对数据的类型以及是否为null等进行校验,确保解析出来数据类型的正确性。对于可能不存在key的时候,还可以设置一些默认值。
举个例子:
block
使用block代替delegate,这个没啥可多说的,把代码变得非常紧凑,减少文件的消息数量,最主要的是关系没那么紧密了。对于有大量的delegate方法才考虑使用protocol实现,这个时候block太多也影响阅读。
同时,对于传递target/selector,也尽量使用block吧,这种阅读查找起来太不方便了。
提交代码
及时stage,这个非常重要,开发过程中经常需要经常比对上一步的代码,这样才能最大程度上确保自己的改动是正确的。如果有一些小问题,也可以即使找到历史版本。
及时commit,每完成一个相对完整的需求,就commit,小提交是个好习惯。
PR code review要做好,要花大量的时间做,有条件的话,最好每个版本开一次总结会。
RAC封装网络请求
返回的signal要避免多次出现side effect,但不使用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:来封装一个可以cancel的请求
iOS代码实践总结的更多相关文章
- 如何把iOS代码编译为Android应用
新闻 <iPhone 6/6 Plus中国销量曝光:单月销量650万>:据iSuppli Corp.中国研究总监王阳爆料,iPhone 6和iPhone 6 Plus在国内受欢迎的情况大大 ...
- CI Weekly #18 | flow.ci iOS 最佳实践出炉,正式支持 Git@OSC 构建
如大家所期待,flow.ci 现已支持开源中国的代码仓库 - 码云,可以直接构建 Git@OSC 的项目了,点击创建项目-选择代码仓库-选择码云-绑定 OSChina 账户-选择要构建项目,教程看这里 ...
- 半年的iOS代码生活
半年的iOS代码生活 在高考大军中拼杀过,也在大学校园中荒芜过,曾经低迷消沉,也常满怀壮志…… 但是最多的还是被称为小伙子以及自称为iOS工程师!博主就是这种喜闻乐见的这类人,实习一年后在2015年的 ...
- 如何把设计图自动转换为iOS代码? 在线等,挺急的!
这是一篇可能略显枯燥的技术深度讨论与实践文章.如何把设计图自动转换为对应的iOS代码?作为一个 iOS开发爱好者,这是我很感兴趣的一个话题.最近也确实有了些许灵感,也确实取得了一点小成果,和大家分享一 ...
- iOS 开发实践之 Auto Layout
原:http://xuexuefeng.com/autolayout/?utm_source=tuicool 本文是博主 iOS 开发实践系列中的一篇,主要讲述 iOS 中 Auto Layout(自 ...
- (转)iOS 最佳实践
本文转自http://www.jianshu.com/p/b0bf2368fb95 感谢作者和译者 iOS最佳实践 iOS最佳实践 译者注 本文翻译自 futurice 公司的 iOS Good Pr ...
- Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!
本文原题“从实践角度重新理解BIO和NIO”,原文由Object分享,为了更好的内容表现力,收录时有改动. 1.引言 这段时间自己在看一些Java中BIO和NIO之类的东西,也看了很多博客,发现各种关 ...
- ReactiveCocoa代码实践之-更多思考
三.ReactiveCocoa代码实践之-更多思考 1. RACObserve()宏形参写法的区别 之前写代码考虑过 RACObserve(self.timeLabel , text) 和 RACOb ...
- ReactiveCocoa代码实践之-RAC网络请求重构
前言 RAC相比以往的开发模式主要有以下优点:提供了统一的消息传递机制:提供了多种奇妙且高效的信号操作方法:配合MVVM设计模式和RAC宏绑定减少多端依赖. RAC的理论知识非常深厚,包含有FRP,高 ...
随机推荐
- 学习练习 java数据库查询小题
10. 查询Score表中的最高分的学生学号和课程号.(子查询或者排序) 11. 查询每门课的平均成绩. 12.查询Score表中至少有5名学生选修的并以3开头的课程的平均分数. 13.查询分数大于7 ...
- C#实现文件下载的几种方法
//WriteFile实现下载 protected void Button2_Click(object sender, EventArgs e) { /* using System.IO; */ st ...
- [AngularJS 1] Introduction to AngularJS
introduction:this article is going to introduce AngularJS in generally. I will write it through five ...
- java swing 使用按钮关闭窗口
目的是给JButton添加点击操作,使指定JFrame窗口关闭. 网上不少说法是采用frame.dispose();的方法 但是采用frame.dispose();并没有使添加在frame上的wind ...
- windows svn
1.1Svn和VisualSvn介绍 VisualSvn Server2.5.6(版本控制服务器)免费开源软件 是基于Windows平台上的Subversion服务器,它是免费的 官方下载: http ...
- 生成ssl证书
http://blog.csdn.net/liuchunming033/article/details/48470575 https://segmentfault.com/a/119000000256 ...
- rem是如何实现自适应中的?
使用rem 然后根据媒体查询实现自适应.跟使用JS来自适应也是同个道理,不过是js更精确一点.使用媒体查询: html { font-size: 62.5% } @media only screen ...
- C# (GDI+相关) 图像处理(各种旋转、改变大小、柔化、锐化、雾化、底片、浮雕、黑白、滤镜效果) (转)
C#图像处理 (各种旋转.改变大小.柔化.锐化.雾化.底片.浮雕.黑白.滤镜效果) 一.各种旋转.改变大小 注意:先要添加画图相关的using引用. //向右旋转图像90°代码如下 ...
- IE 不兼容的几个js问题及解决方法
IE 不兼容的几个js问题及解决方法 1 Table的问题 在动态新增tr或者td时,createElecment() 一般用appendChild();都不生效,解决办法是用新增tbody, 如 ...
- QQ聊天信息提取
先前在iOS 8.x版时,往往未能顺利取出QQ的聊天信息,即使顺利取出数据库,却发现聊天信息已被加密处理,仅只能得知是与哪些QQ号进行聊天,而未能顺利得知聊天内容. 但这个情况到后来有了变化,以下情境 ...