首页 >iOS开发

如何接手一个老旧的iOS项目

2017-02-20 10:27 编辑: suiling 分类:iOS开发 来源:点燃那火焰 投稿

57.jpg

作者:点燃那火焰

续命

一个程序员最气愤的事是什么?没有文档!

一个程序员最讨厌的事是什么?写文档!

以上被称为“程序员的纠结”,Unbelievable!最近我就赶上这么纠结的事了,公司有一个项目,姑且称之为“起死回生项目”吧,成立于两年前,当时已经上架,但效果不好,又被迫下架关项目,但是人生就是这么奇妙,两年后时来运转,市场迫切需要的东西正是这个“起死回生项目”,于是它+1s,咳咳,不,是起死回生了,然而当时所有的项目组成员均已离职,没有文档!没有人交接!

谁来接手呢?领导说交给我,笔者当时正在另一个项目组上,当时我就说我一个其他项目组的,怎么能去起死回生项目组呢,领导对我说:“组织上已经决定了,由你来当总书。。。咳咳,由你来重启这个项目”。我心中无限感慨,当时就念了两句诗:“苟利国家生死以。。。咳咳”

于是我就来到了这个坑爹的项目组,哦,对了该项目组只有我一个人!

而此时的我,刚刚学会iOS一年。。。。。

面临的问题

作为一个有责任有担当的新世界好青年,组织既然已经决定了,我又是党员,这时候我不上谁上呢?!带着这样的决心,我开始了奇妙的重启之旅。

首先,获得带权限,从服务器上下载来代码,发现项目竟然运行不起来!?

一大堆Warning、Error!!

what!?

不是说已经上线了吗?我看了下提交日志,最后一条是修改build号,发布到App Store了,怎么会跑不起来呢?

仔细再看报错,大多数是缺少文件类的,就是缺少framework、第三方库之类的,但是奇怪的是之前确实可以跑得啊,于是通过N层关系找到原来的项目组成员,是的,在电话中得知,这个项目的依赖库管理当时采用的是静态库方法,如今的小伙伴用cocoapods用的飞起,一句pod update搞定所有,然而当年,我不知道cocoapods发布了没有,至少这个项目没有采用,那么如何管理众多的第三方库和组件呢?

答案是另起一个项目,把所需组件和库,编译成一个CommonLib Frameworks,再在主项目中导入framework。如今网上还能找到怎么制作framework的文档,不再赘述,大家找吧。

如下图,这是我已经导入了一部分的截图

01.png

缺少的Framework

原来是没有导入Framework,才会出现的error,知道原因就好办,我又找到这个Framework的代码,加入进来,果然OK了,至此,我们终于build success。

同时也发现,该项目当时支持的最低版本是iOS 6.1,嗯,是的,在iOS 10.2发布的时候,这个项目还停留在6.1

2.png

最低支持版本

撸起袖子,加油干

好了,吐槽完了,该干的还得干呢,于是我开始思考,如何接收这样的项目呢?

上干货!

3.png

升级迁移计划

以上是我的思维导图,当然是边做边补充的,就像阿甘说的,你永远不知道下一个坑在哪里,走一步看一步了

下面就按照思维导图的顺序来做一一介绍!

1.迁移到Cocoapod

1.1 问题

  • 依赖库不易管理,自己人肉的方式管理依赖库的痛苦大家都懂,这也是cocoapods盛行的原因

  • 没有版本管理,你只能去打开工程,肉眼分辨当时用的是哪个版本,例如MJRefresh已经更新到3.0,而项目中的是什么版本呢,不知道

解决方法当然就是如标题所说,迁移到Cocoapod

1.2 迁移

思维导图上,可以看到我面临的问题

  • 依赖库统计

我必须知道项目中使用到了哪些第三方库,哪些有Cocoapods版本,哪些没有,这部分,我采用的本办法,打开这个工程,人肉统计,有下图这么多!!!

 

4.png

依赖库

呵呵!

没办法,只能去这么做,这时候我们又发现,这么多第三方库,有些是没有用到的,如何发现?就是去全局搜索咯

我猜是某些组件不再用了,但是没有及时清理,于是留给后人很多无用代码,也提醒读者,注意这一点,不要害人害己啊

最终清理后的组件,不如上图这么多,大概二十个左右,整理成一个podfile文件,如下

注意,这里每个组件名后,都有指定版本号,在下一节切换中有提到,如何确定版本号

5.png

podfile

这部分用到最多的就是cocoapods的搜索功能

pod search AFNetworking 

  • 切换

现在准备工作完毕,就是要切换了!

首先,需要去除工程配置文件里面的很多配置项,原则就是静态库需要配置上的,这里都得删除,目标是把就项目中的所有相关CommonLibARC的配置清理干净

例如下面的,配置很多很杂我这里就不一一截图,仅上图2张供参考

6.png

图1

7.png

图2

然后,就是Cocoapods的部分了,网络上有大量教程,我们需要建立podfile,再用pod install命令。这样会生成一个xcworkspace文件,以后需要从这里打开工程,如下图,所有第三方组件被管理在Pods工程下

8.png

目录

于此同时,这时也会产生大量的error,原因是podfile中需要制定库的版本,而我们并不知道两年前使用的什么版本,随着版本的变化,方法名可能会变动,甚至被干掉,这样build必然失败。

我的方法是:

1.用pod search,看看组件有哪些版本,先选一个比较稳定的大版本,比如,MJRefresh最新是3.1.12,从版本号和时间推算,当时工程中肯定是特别老的,可能是1.x,但是考虑到时间推移,组件也是有必要的升级,我最终选择了3.0版本,这个版本使用者多,且并不算太老旧

9.png

pod search

果然产生大量方法名变动,没辙,自己人肉一一修改,举例如下

10.png

MJRefresh 方法名变动

2. error层出不穷

我进行到这一步是最痛苦的,因为连续几天都是无法编译成功的,因为当编译器遇到一些error后就不在往下编译了,因此你看到比如3个error,解决了他们,再build,发现又多出来5个,这种更多是精神上的折磨,耐性的消磨。而且我没法提交代码,因为每次我只解决了一部分问题,只能先暂存到本地,到编译成功了才能正式提交到remote。

3. CocoaLumberjack--->DDLog

这里特别要提一下CocoaLumberjack,这是一个集快捷、简单、强大和灵活于一身的日志框架,我们主要用到的是DDlog,这是项目原本就在用的组件,但是DDlog需要定义一个日志级别,低于这个级别的log不再显示。

在源代码中,是每个用到的地方都在.m文件都定义一次,全局搜索会发现很多同样的代码, 显得啰嗦

11.png

很多重复定义

12.png

定义日志级别

因此,我将其改到了PCH文件中,在appdelegate中定义其级别,这样只需一次,全局通用

4. Crashlytics

这里多提一次Crashlytics,这是个非常好用的统计Crash的工具,植入方便,使用简洁,5分钟搞定,强烈安利一发。原项目也有使用,不过账号密码没有移交下来,我乘此机会将其更新到了最新版本

好了,至此,所有的迁移工作已经完成,我兴奋的按下Command+B,Build Success!!!

然而茫茫多的warning还没有解决呢

5. 组件过渡方法

有部分组件,牵扯范围广,替换起来特别麻烦,为了不影响进度此时我决定,先跑起来,后面再慢慢优化,于是,采用的了过渡方法,先不用cocoapod管理,但是Framework必须删除,否则又会有冲突,怎么办呢

简单粗暴的拖拽,直接把组件文件夹add进project!

先跑起来再说啊!

2 iOS 8.0 升级

13.png

 最低版本

此时的项目,历经岁月的沧桑,他终于跑起来了,但是项目依然停留在历史的那个时刻,iOS 6.1!!!!

然后,我们再到苹果官网,查看各版本占比,如图,截止2017.01.04号,额,已经看不到8以下的,加一块才6%

14.png

iOS版本占比

于是,我又开始了新的征程,升级到iOS 8.0,为啥是8.0呢?

步子大了怕扯到蛋!

如最上面的思维导图所示,warning大部分集中在以下几个地方

2.1  Method Deprecation

苹果也是个坑爹的公司,随着iOS版本的升级,需要系统原生的类和方法都发生了变化,有了新的方法替代,系统会提示我们改掉,又是个苦力活,没办法一一去改吧,此项目中碰到最多的是,UILabel的方法,用于计算字体大小

16.png

deprecated warning

17.png

deprecated warning

2.2 Format String Issue

这类比较好解决,大多数是系统可以帮忙解决,相信你们一定碰到过

 18.png

Format String Issue

2.3 Conflicting Return Type

这个和第一种类似,都系统方法升级导致的,依照提示改过来即可

19.png

Conflicting Return Type

2.4 missing [super awakeFromNib]

这是坑爹的代码风格导致,所有的UITableViewCell都没有[super awakeFromNib]方法。

大家知道,我们在工程实践中有RootClass,比如RootTableViewCell,所有cell都继承于RootClass,在RootClass实现一些基本的、全局的属性和方法,通常我们写的就是awakeFromNib方法,缺失这个方法使得所有子类没有继承到父类的全局设置。

没二话,必须加上

20.png

missing [super awakeFromNib]

21.png

missing [super awakeFromNib]

2.5 ARC bug

这个Bug估计是手抖写错了,但是排查起来挺麻烦的。

现象是某个页面在debug的时候正常,但是打包安装到手机上就会闪退,由于调试时一直是好的,所以抓不到Crash,这时候就是Crashlytics大显身手的时候,我们登录Crashlytics的网站,可以清楚的看到崩溃日志,如下图,这里显示崩溃的原因是unrecognized selector,也就说一个对象调用了未知方法,按照Log指示,其实是一个NSDictionary调用了 objectAtIndex:方法

26.png

Crashlytics崩溃日志

再仔细看下去其实日志中还有具体crash的行数在305行,这行代码如下:

27.png

代码

确实是调用了objectAtIndex方法,可是这个yLabelsStringArray确实是一个NSArray啊!怎么会变成NSDictionary了呢?错在哪里呢

28.png

数组属性

仔细看的同学会发现,这个属性的ARC参数是assign,大家知道assign是用于非指针变量,用于基础数据类型,并不会对引用计数+1,也就说这个NSArray被创建后,就立马被释放了,这才导致崩溃,如此只有改成strong即可

 29.png

改成strong

至此,整个项目build success!!

3 增加Log

好了,我们已经将一个老旧的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

37.png

3.2 Network Request Log

同样的,我们对于接口调用也是一无所知,必须在网络也打上Log,为了方便调试,我打印出了URL、Method(GET/POST)、参数、Header

多提一句为啥打印出Header,因为Header中有access token,这样我们可以使用Postman来做需要登录的接口调试,而不必在app点击来触发请求

38.png

Network Log

同样的,安利一下Postman,非常好用的网络调试工具

30.png

Postman

3.3 DB file path

工程中还有数据库,在用模拟器调试的时候,其实我们是可以打开运行中App的数据库的,只需打印出database的文件路径即可,方便我们未来的调试

4 无效代码移除

Log完成后,我们可以安心的来整理业务逻辑了。

跟需求方沟通后,app中有些功能完全可以移除掉了,接下来就是删代码的节奏了

这部分比较简单,无用代码找出来,Delete即可,还是苦力活

5 工程整理

5.1 模块化目录

工程中的目录结构也是一个需要注意的地方,我采用的是使用最广泛的MVC结构

 40.png

目录结构

5.2 网络层整理

工程采用的是最常见的AFNetworking,这部分依然是体力活

  • 首先需要整理出服务器地址,包括Dev(开发)、Stg(上线)、Pro(生产)三个环境

  • 需要整理出所有的接口文档,这就是靠Network Log了

  • 网络层逻辑整理,需要阅读代码,理解原本是怎么构建出网络层的,这部分可以暂放一会

5.3 数据库整理

项目的许多数据是存在数据库里面的,从依赖库来看,使用的是FMDB,也是比较常见的开源库

我使用的是MesaSQLite作为client

这部分工作,需要有人配合,暂时放一放

6 storyboard臃肿

原项目中,所以的ViewController,注意,是所有的ViewController都在一个storyboard里,所以当你想打开这个storyboard时,需要等,嗯,大约3min,给大家看一看,注意最下面我已经缩放到6.25%了,这样一个庞大、臃肿的storyboard,即不好管理,也不会扩展,打开它还有严重的卡顿

71.png

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,如何迁移过去是个麻烦的事

搜索CocoaChina微信公众号:CocoaChina
微信扫一扫
订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina
我要投稿   收藏文章
上一篇:一步一步构建你的iOS网络层 - TCP篇
下一篇:iOS支付项目实操分享
我来说两句
发表评论
您还没有登录!请登录注册
所有评论(0

综合评论

相关帖子

sina weixin mail 回到顶部