作者: 点燃那火焰
一个程序员最气愤的事是什么?没有文档!
一个程序员最讨厌的事是什么?写文档!
以上被称为“程序员的纠结”,Unbelievable!最近我就赶上这么纠结的事了,公司有一个项目,姑且称之为“起死回生项目”吧,成立于两年前,当时已经上架,但效果不好,又被迫下架关项目,但是人生就是这么奇妙,两年后时来运转,市场迫切需要的东西正是这个“起死回生项目”,于是它+1s,咳咳,不,是起死回生了,然而当时所有的项目组成员均已离职,没有文档!没有人交接!
谁来接手呢?领导说交给我,笔者当时正在另一个项目组上,当时我就说我一个其他项目组的,怎么能去起死回生项目组呢,领导对我说:“组织上已经决定了,由你来当总书。。。咳咳,由你来重启这个项目”。我心中无限感慨,当时就念了两句诗:“苟利国家生死以。。。咳咳”
于是我就来到了这个坑爹的项目组,哦,对了该项目组只有我一个人!
而此时的我,刚刚学会iOS一年。。。。。
作为一个有责任有担当的新世界好青年,组织既然已经决定了,我又是党员,这时候我不上谁上呢?!带着这样的决心,我开始了奇妙的重启之旅。
首先,获得带权限,从服务器上下载来代码,发现项目竟然运行不起来!?
一大堆Warning、Error!!
what!?
不是说已经上线了吗?我看了下提交日志,最后一条是修改build号,发布到App Store了,怎么会跑不起来呢?
仔细再看报错,大多数是缺少文件类的,就是缺少framework、第三方库之类的,但是奇怪的是之前确实可以跑得啊,于是通过N层关系找到原来的项目组成员,是的,在电话中得知,这个项目的依赖库管理当时采用的是静态库方法,如今的小伙伴用cocoapods用的飞起,一句pod update搞定所有,然而当年,我不知道cocoapods发布了没有,至少这个项目没有采用,那么如何管理众多的第三方库和组件呢?
答案是另起一个项目,把所需组件和库,编译成一个CommonLib Frameworks,再在主项目中导入framework。如今网上还能找到怎么制作framework的文档,不再赘述,大家找吧。
如下图,这是我已经导入了一部分的截图
缺少的Framework
原来是没有导入Framework,才会出现的error,知道原因就好办,我又找到这个Framework的代码,加入进来,果然OK了,至此,我们终于build success。
同时也发现,该项目当时支持的最低版本是iOS 6.1,嗯,是的,在iOS 10.2发布的时候,这个项目还停留在6.1
最低支持版本
好了,吐槽完了,该干的还得干呢,于是我开始思考,如何接收这样的项目呢?
上干货!
升级迁移计划
以上是我的思维导图,当然是边做边补充的,就像阿甘说的,你永远不知道下一个坑在哪里,走一步看一步了
下面就按照思维导图的顺序来做一一介绍!
1.1 问题
依赖库不易管理,自己人肉的方式管理依赖库的痛苦大家都懂,这也是cocoapods盛行的原因
没有版本管理,你只能去打开工程,肉眼分辨当时用的是哪个版本,例如MJRefresh已经更新到3.0,而项目中的是什么版本呢,不知道
解决方法当然就是如标题所说,迁移到Cocoapod
1.2 迁移
思维导图上,可以看到我面临的问题
依赖库统计
我必须知道项目中使用到了哪些第三方库,哪些有Cocoapods版本,哪些没有,这部分,我采用的本办法,打开这个工程,人肉统计,有下图这么多!!!
依赖库
呵呵!
没办法,只能去这么做,这时候我们又发现,这么多第三方库,有些是没有用到的,如何发现?就是去全局搜索咯
我猜是某些组件不再用了,但是没有及时清理,于是留给后人很多无用代码,也提醒读者,注意这一点,不要害人害己啊
最终清理后的组件,不如上图这么多,大概二十个左右,整理成一个podfile文件,如下
注意,这里每个组件名后,都有指定版本号,在下一节切换中有提到,如何确定版本号
podfile
这部分用到最多的就是cocoapods的搜索功能
pod search AFNetworking
切换
现在准备工作完毕,就是要切换了!
首先,需要去除工程配置文件里面的很多配置项,原则就是静态库需要配置上的,这里都得删除,目标是把就项目中的所有相关CommonLibARC的配置清理干净
例如下面的,配置很多很杂我这里就不一一截图,仅上图2张供参考
图1
图2
然后,就是Cocoapods的部分了,网络上有大量教程,我们需要建立podfile,再用pod install命令。这样会生成一个xcworkspace文件,以后需要从这里打开工程,如下图,所有第三方组件被管理在Pods工程下
目录
于此同时,这时也会产生大量的error,原因是podfile中需要制定库的版本,而我们并不知道两年前使用的什么版本,随着版本的变化,方法名可能会变动,甚至被干掉,这样build必然失败。
1.用pod search,看看组件有哪些版本,先选一个比较稳定的大版本,比如,MJRefresh最新是3.1.12,从版本号和时间推算,当时工程中肯定是特别老的,可能是1.x,但是考虑到时间推移,组件也是有必要的升级,我最终选择了3.0版本,这个版本使用者多,且并不算太老旧
pod search
果然产生大量方法名变动,没辙,自己人肉一一修改,举例如下
MJRefresh 方法名变动
2. error层出不穷
我进行到这一步是最痛苦的,因为连续几天都是无法编译成功的,因为当编译器遇到一些error后就不在往下编译了,因此你看到比如3个error,解决了他们,再build,发现又多出来5个,这种更多是精神上的折磨,耐性的消磨。而且我没法提交代码,因为每次我只解决了一部分问题,只能先暂存到本地,到编译成功了才能正式提交到remote。
3. CocoaLumberjack--->DDLog
这里特别要提一下CocoaLumberjack,这是一个集快捷、简单、强大和灵活于一身的日志框架,我们主要用到的是DDlog,这是项目原本就在用的组件,但是DDlog需要定义一个日志级别,低于这个级别的log不再显示。
在源代码中,是每个用到的地方都在.m文件都定义一次,全局搜索会发现很多同样的代码, 显得啰嗦
很多重复定义
定义日志级别
因此,我将其改到了PCH文件中,在appdelegate中定义其级别,这样只需一次,全局通用
4. Crashlytics
这里多提一次Crashlytics,这是个非常好用的统计Crash的工具,植入方便,使用简洁,5分钟搞定,强烈安利一发。原项目也有使用,不过账号密码没有移交下来,我乘此机会将其更新到了最新版本
好了,至此,所有的迁移工作已经完成,我兴奋的按下Command+B,Build Success!!!
然而茫茫多的warning还没有解决呢
5. 组件过渡方法
有部分组件,牵扯范围广,替换起来特别麻烦,为了不影响进度此时我决定,先跑起来,后面再慢慢优化,于是,采用的了过渡方法,先不用cocoapod管理,但是Framework必须删除,否则又会有冲突,怎么办呢
简单粗暴的拖拽,直接把组件文件夹add进project!
先跑起来再说啊!
最低版本
此时的项目,历经岁月的沧桑,他终于跑起来了,但是项目依然停留在历史的那个时刻,iOS 6.1!!!!
然后,我们再到苹果官网,查看各版本占比,如图,截止2017.01.04号,额,已经看不到8以下的,加一块才6%
iOS版本占比
于是,我又开始了新的征程,升级到iOS 8.0,为啥是8.0呢?
步子大了怕扯到蛋!
如最上面的思维导图所示,warning大部分集中在以下几个地方
苹果也是个坑爹的公司,随着iOS版本的升级,需要系统原生的类和方法都发生了变化,有了新的方法替代,系统会提示我们改掉,又是个苦力活,没办法一一去改吧,此项目中碰到最多的是,UILabel的方法,用于计算字体大小
deprecated warning
deprecated warning
这类比较好解决,大多数是系统可以帮忙解决,相信你们一定碰到过
Format String Issue
这个和第一种类似,都系统方法升级导致的,依照提示改过来即可
Conflicting Return Type
这是坑爹的代码风格导致,所有的UITableViewCell都没有[super awakeFromNib]方法。
大家知道,我们在工程实践中有RootClass,比如RootTableViewCell,所有cell都继承于RootClass,在RootClass实现一些基本的、全局的属性和方法,通常我们写的就是awakeFromNib方法,缺失这个方法使得所有子类没有继承到父类的全局设置。
没二话,必须加上
missing [super awakeFromNib]
missing [super awakeFromNib]
这个Bug估计是手抖写错了,但是排查起来挺麻烦的。
现象是某个页面在debug的时候正常,但是打包安装到手机上就会闪退,由于调试时一直是好的,所以抓不到Crash,这时候就是Crashlytics大显身手的时候,我们登录Crashlytics的网站,可以清楚的看到崩溃日志,如下图,这里显示崩溃的原因是unrecognized selector,也就说一个对象调用了未知方法,按照Log指示,其实是一个NSDictionary调用了 objectAtIndex:方法
Crashlytics崩溃日志
再仔细看下去其实日志中还有具体crash的行数在305行,这行代码如下:
代码
确实是调用了objectAtIndex方法,可是这个yLabelsStringArray确实是一个NSArray啊!怎么会变成NSDictionary了呢?错在哪里呢
数组属性
仔细看的同学会发现,这个属性的ARC参数是assign,大家知道assign是用于非指针变量,用于基础数据类型,并不会对引用计数+1,也就说这个NSArray被创建后,就立马被释放了,这才导致崩溃,如此只有改成strong即可
改成strong
至此,整个项目build success!!
好了,我们已经将一个老旧的iOS工程,成功升级成最常用的工程,但是迄今我们甚至没有去了解整个项目是干嘛的,也就是说我们根本没有了解到业务逻辑,由于文档缺失,如何掌握业务逻辑成了大麻烦,我们只能通过自己把玩这个App,每个页面去点点,去操作。于是为了方便快速掌握代码架构和业务了解,需要Log来帮助我们理解项目
而原工程几乎没有任何Log。。。。
3.1 ViewWillAppeared/ViewdidLoad
所幸的是,这个工程的所有的ViewController都是继承于一个RootController,于是在BaseViewController中,加入log,这我们在Debug时,随着操作,我们就能知道哪个页面对应哪个ViewController,这里就用到了上面提到的DDLog
#ifdef DEBUG DDLogInfo(@"%@ viewDidLoad", [self class]); #endif #ifdef DEBUG DDLogInfo(@"%@ viewWillAppear", [self class]); #endif
3.2 Network Request Log
同样的,我们对于接口调用也是一无所知,必须在网络也打上Log,为了方便调试,我打印出了URL、Method(GET/POST)、参数、Header
多提一句为啥打印出Header,因为Header中有access token,这样我们可以使用Postman来做需要登录的接口调试,而不必在app点击来触发请求
Network Log
同样的,安利一下Postman,非常好用的网络调试工具
Postman
3.3 DB file path
工程中还有数据库,在用模拟器调试的时候,其实我们是可以打开运行中App的数据库的,只需打印出database的文件路径即可,方便我们未来的调试
Log完成后,我们可以安心的来整理业务逻辑了。
跟需求方沟通后,app中有些功能完全可以移除掉了,接下来就是删代码的节奏了
这部分比较简单,无用代码找出来,Delete即可,还是苦力活
5.1 模块化目录
工程中的目录结构也是一个需要注意的地方,我采用的是使用最广泛的MVC结构
目录结构
5.2 网络层整理
工程采用的是最常见的AFNetworking,这部分依然是体力活
首先需要整理出服务器地址,包括Dev(开发)、Stg(上线)、Pro(生产)三个环境
需要整理出所有的接口文档,这就是靠Network Log了
网络层逻辑整理,需要阅读代码,理解原本是怎么构建出网络层的,这部分可以暂放一会
5.3 数据库整理
项目的许多数据是存在数据库里面的,从依赖库来看,使用的是FMDB,也是比较常见的开源库
我使用的是MesaSQLite作为client
这部分工作,需要有人配合,暂时放一放
原项目中,所以的ViewController,注意,是所有的ViewController都在一个storyboard里,所以当你想打开这个storyboard时,需要等,嗯,大约3min,给大家看一看,注意最下面我已经缩放到6.25%了,这样一个庞大、臃肿的storyboard,即不好管理,也不会扩展,打开它还有严重的卡顿
storyboard
这部分我暂时写一下思路,因为还没有实施
6.1 方案一:storyboard reference
为了解决这个问题,在 iOS 9 中苹果介绍了 Storyboard References 这个概念。Storyboard References 允许你从 segue 中引用其他 storyboard 中的 viewController。这意味中你可以保持不同功能模块化,同时 Storyboard 的体积变小并易与管理。不仅容易理解了,和团队一起工作时,合并(工作成果)也变的简单了。
但是我们的项目目前还在8.0,需要考虑一下是否升级到9.0
6.2 方案二:storyboard拆分成xib
Storyboard的好处是,可以一眼看出app的逻辑架构,对于理解整个项目各个页面之间的跳转是有好处的,但是坏处显而易见,他无法管理一个庞大的项目,因此很多工程采用的方案是xib,一个ViewController,对应一个xib,摒弃了storyboard,这样扩展起来特别方便
但是问题是:原工程storyboard中,可能有三十多个ViewController,如何迁移过去是个麻烦的事