iOS 重构代码 – 我是如何删掉 6 万行代码并且不删减原有功能的

释放双眼,带上耳机,听听看~!
  •  

    我列个列表吧:

     

    • 删除没用到的第三方库
    • 删除不合理的第三方库,使用系统自带的或者自己造轮子
    • 删除定义好但是没有用到的变量
    • 删除 import 进来但是没有用到的头文件
    • 删除更旧项目留下来的用不到的逻辑
    • Controller 层不合理的层级结构重构,无用代码清理
    • View 层不合理的结构重构
    • Service 层冗余的写法重构
    • Model 层不合理的写法重构
    • 拆开不合理的耦合
    • 耦合一个类别的模块
    • 修复了多处内存泄露
    • 修复了多处循环引用
    • 优化编译速度
    • 消除项目中的 warning

     

    关于删除代码,在某个项目里,Pods 文件夹那些第三方库的代码删了 9 万多行(那个目录没有被 git ignore 掉),项目里面删除了大约 4 万行,其中大量代码是该项目之前的项目里面留下来的东西,只不过没人清理。在删了 4 万行之后,程序仍然能完整的跑。

     

    接下来是做了部分重构,把一些第三方库删掉,自己造轮子,在这个过程中,累计删除了 1.2 万行代码,增加了 1100 行左右。

     

    整个重构工作下来,编译速度从 2-3 分钟减小到了 40 多秒,warning 从 70 多减少到了 0,第三方库的数量从 51 个减少到了 13 个,安装包从 22.1M 减小到了 3.7M,功能反而比之前还要多。

     

    内存泄露方面,因为没人在意这件事,有一个功能使用一次,就会增加好几百 kb 内存,那部分代码是用 C 写的,所以及时释放内存,并且优化下调用方式,内存泄露的问题就完美解决。

     

    循环引用方面,是因为有人把 Xcode 的 warning 关了,后来打开的时候,发现了四个循环引用 + 几十个 warning,并且测试过程中发现那个页面不断打开退出,程序会 crash。

     

    笼统的就这么多,我再来分享几个具体的点。

     

     

    1.避免滥用单例

     

    单例用着确实爽,但是程序退出之前是不会被回收的,如果是整个生命周期基本用不到的模块做成单例,那么只会浪费内存而已。

     

    2.避免无用的层级

     

    具体是什么意思呢,用网络层举例子,封装 AFN 是一层,API 的后缀字符串放一层,构造请求放一层,OAuth 授权放一层,发普通请求又是一层。增加一个 API,至少要修改 6 个文件。写着也很痛苦,看着也很痛苦啊。

     

    网络层就只设计一层,封装 AFN,发请求的函数也在里面,API 地址直接用字符串写进去,搞那么多层没实际意义,在这么小的一个项目里面。

     

    除此之外,关于项目文件结构,一两个文件的建议不要新建文件夹放进去,这个主要是个人习惯,其实无大碍。

     

    3.合理设计方法名

     

    不留隐患

     

    - (void)requestAtPathForRouteNamed:(NSString *)routeName object:(id)object parameters:(NSDictionary *)parameters

     

     

    - (void)requestWithMethod:(XXHTTPMethod)method path:(NSString *)path params:(id)params paramsType:(XXParamType)paramsType

     

     

    之前这样设计的目的是 param 放 form data 类型数据,object 放 json 格式,显然不合理,同一个 API 不应该允许同时存在 form data 和 json,如果采用第一种,新来的同事可能会认为这两个都可以填数据,这是不符合我们期望的。

     

    甚至再极端一点,某天我们需要传文件过去,是不是还得再扩充字段。

     

    如果采用第二种,param 是 id 类型,如果是 json,type 传入 json 枚举类型,如果是二进制,type 传入二进制枚举类型,只留一个字段暴露给开发者更合理。

     

    4.避免耦合

     

    我们的项目中有一条渐变颜色的线要到处用到,这条线我们放在了 UIImage+XXUtil.h 里面,之前的设计是这样的:

     

    + (instancetype)xx_navigationBarShadowImage

     

     

    在 .m 的实现中,还把 UIColor+XXTheme 耦合进去了,并且这个方法已经脱离了类名 Util 的实质,他已经不是一个通用的工具了,重构之后的命名是这样的:

     

    + (instancetype)xx_gradientImageWithStartColor:(UIColor *)aColor endColor:(UIColor *)bColor andWidth:(CGFloat)width

     

     

    这样就很符合 Util 这个 category 名字。

     

    5.避免滥用继承

     

    继承确实很好用,带来的后果就是子类会把父类的方法挨个执行一遍,乍一看没什么,但是如果这个方法很消耗性能呢。

     

    我们这个项目就遇到了,app 经常卡死,用着用着,就 freeze 了,点哪里都没反应。因为所有页面都继承自基类的一个设计,恰好基类里面有一个比较耗时的操作,每个页面都会执行至少三次,就导致了页面假死。

     

    重构后的做法是设计成一个 category,只是给 UIViewController 添加了几个方法,按需调用,不需要在每个页面都调用,于是解决了这个诡异的 bug。

     

    6.合理选择第三方库

     

    如果有一个功能,迫于各种原因,不得不采用第三方库,至少也要选一个 GitHub 上 star 比较多的吧,其次是看看 issue 列表有没有什么很严重的 bug 没修好,以及兼容性问题,多养成好习惯,慢慢就能筛选出来最合适的库了。

     

    7.避免滥用第三方库

     

    我们的项目之前有用到 YYText 这个库,就为了一段文字里面加一张图片,活动当天 iOS9 设备出现好几百次 crash,实际上这段代码用 NSAttributedString attributedStringWithAttachment 写一下,七行就够了,七行替代掉一个不稳定的第三方库,还是很划算的。

     

    不知道因为什么原因,可能是更旧的项目里面用了 PSCollectionView,能跑就没去重构,这类库也是属于完全没必要的,系统自带的足够好用,并且更安全。

     

    8.各司其职

     

    数据的处理,比如字符串进行 UTF8 编码,时间戳转成 YYYY-MM-DD 字符串,这些都放在 Model 层来处理,各司其职,Model 层就是做数据处理的。

     

    9.结合实际需求

     

    后端把各种 ID 用 long 型来记录,是因为他们要做索引,为了索引速度。而客户端完全没这个需求,直接用 string 就好,还不用担心长度不够的溢出,做展示的时候还不用转类型。

     

    同样的,金额按理说应该用双精度浮点型,因为 float 的精度不够,结合我的开发经验看,金额很少要客户端做加减,直接用 string 即可,需要计算的时候再转换,只转换一次,避免丢失精度。

     

    10.关于命名规范

     

    苹果的 UIKit 就是最好的例子,写什么组件不知道名字怎么起的时候,就想想苹果有没有类似的组件,去找找灵感。

     

    11.避免无意义的注释

     

    OC 的方法名本身就很长很清晰了,只是给方法名中间加几个空格,然后作为注释,跟没写一样吧。

     

    12.不用的代码删掉

     

    Git 的作用就是随时回溯以前版本,代码都是能找到的,把代码注释掉,再写一行类似的,除了增加阅读成本,容易引起歧义,应该没什么用了。一个文件一共一两百行,打开之后发现七八十行代码被注释了,这种感觉相当操蛋,影响阅读。

     

     

     

为TA充电
共{{data.count}}人
人已赞赏
iOS文章

iOS 键盘只能输入字母和数字

2021-5-6 9:54:57

iOS文章

iOS开发:Objective-C中nil、Nil与NULL的区别

2021-5-6 10:13:43

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索