iOS专项总结

  一个应用经过多次迭代后告一段落,接下去我们在技术上还可以做些什么呢?答案是提高代码的整体质量。关于这方面,除了我们常喊的 重构,测试也非常重要。

  博主近期给我们的 iOS客户端代码来了一次专项测试。主要从常规的 辅助测试 入手,来了次代码大清理,找到代码中的问题,并一一改掉它们。惊喜的是,这对于提高本人的代码水平有很大的帮助。其实,这套代码的质量本身已经很高了,也非常整洁。而这主要得益于严格的代码规范和pull request机制。

  关于测试,App常关注的往往是一些功能性的,包括单元测试,用monkey在界面上点击看页面表现是否正确等等。 我以前还搭过一个 aspectJ + robotium。(这是Java上的) 然而,测试更应该覆盖代码质量,性能检测等等。
  下面给出一副我理解测试的结构图:

  点击图片可放大缩小。

顺着这幅图,我们可以从静态测试入手。

关于 analyze

在Xcode 中执行product -> analyze工具,出现了粗粗细细的箭头标识表示调用逻辑。

这个工具所找出的主要是代码中的逻辑问题,本身能发现的问题很多。实际运用到工程代码上,发现的问题主要分为以下四点:

  • 比如初始化赋值未被使用过。
  • 比如未调用super语句。
  • 比如从未使用过的变量。
  • 比如逻辑上错误的代码。(这点极少)

但是关于资源文件,该工具是扫描不出来的。另外,我们可以在Xcode中配置该工具。

Clang 静态分析器

analyze 的底层其实就是 Clang 静态分析器,包含 ’shallow’ 和 ‘deep’ 深浅两种模式。

深模式比浅模式慢很多,因为多出了跨方法的 控制流分析 以及 数据流分析。(如前文那副图)

建议:
开启分析器的 全部检查(方法是在 build setting 的 “Static Analyzer” 部分开启所有选项)
在 build setting 里,对 release 的 build 配置开启 “Analyzer during ‘Build’“。(真的,一定要这样做 — 你不会记得手动跑分析器的。)
把 build setting 里的 “Model of Analysis for ‘Analyze’“设为 Shallow(faster)
把 build setting 里的 “Model of Analysis for ‘Build’“设为 Deep

但这两种对于公司这么一个大项目来说都不快,假如在 build setting里 开启 Model of Analysis for ‘Build’ 并设为浅模式,那么每次运行就会整体跑一遍静态分析并不合理。

还有一种设想是在打 ad hoc 包前自动跑一遍 静态分析,是比较合理的也可以在XCode中开启。但是我们编译打包用的是自动化工具,服务器直接从github读取而不是通过XCode,会跳过这个分析。
比较合理的做法是定期 跑一遍 analyzer 工具。(比如每周五)而不是 build , release 以前做这事。(问了下其他同事的意见)

目前静态分析在工程中有 这么几块不改的问题: 首先是网上拷过来的算法。包括加密算法,模糊算法,64位编译算法等等。里面有一些远大于,远小于,与运算等。看不懂的地方不改。

为什么不改呢?因为没看出逻辑问题。(或者看懂了也不敢改,怕算法错误。)

Slender

slender 是一款针对 iOS 图片分析的 Mac 软件。导入工程,可以找到所有未使用到的 resource。(unused 这软件也可以找到多余的资源)

除此以外,它还给出了所有资源文件的建议。包括你缺省的 x3, x2图片版本等。

Faux Pas

意外发现的惊喜。Faux Pas 是一个出色的静态 Error 检测工具。

slender 找到的是未使用的资源,faux 找到的是代码中使用到,但不存在的image。(实测下来往往有很多)

甚至由此 发现了一些弃用的文件夹。

不仅如此,它有一百多个error 和 warning 规则。包括 未定义的 user define runtime attributes(跑在runtime上)。这一点平时靠程序员的肉眼狠难见。曾经你为了图省事直接在 xib 中炫技,加上了一些 key。然而当代码变迁,删掉一个类的时候,根本想不到 到 xib中去删除 对应的部分。

还有一些代码规范上的问题。

比如界面上的文字未本地化;在release 版本依旧有NSLog 输出;NSPhotoLibraryUsageDescription在info.plist中未定义;被定义成strong类型的delegate等等。

和一些配置上的建议。

Warning

你的pods 中还在报 warning 吗?你可以使用下面这种方法屏蔽它们。

在podfile 的开头加上这样一句话:

inhibit_all_warnings!

就能屏蔽 pods 中所有代码上的 warning了。

然后可惜的是,对于编译上的 warning却束手无策。注:iOS中不能阻止 Xcode 报warning 的选项。只能屏蔽某一文件,或某工程的warning。比如我们已屏蔽的pods中 代码的错误。但是屏蔽不了系统在编译过程中 非代码级别的 警告。

并且也不建议屏蔽这种警告,比如由于友盟未支持 7.0 版本所导致的 警告。现在由于 友盟本身未支持,7.0 用户还有许多等等原因,但未来或许能解决。不应忘记这点。

Leaks

苹果集成了一个性能检测工具,叫instruments。使用instrument中的Leaks工具:

iOS内存泄露点比较少,大部分都是系统内部方法,或者一些库里的。我们对此别无办法。实测下来,主要的泄露场景集中在 UIKeyboard 上。在我尝试了各种点击场景后,系统弹框时候最容易泄露。

假如你的代码中真有泄露,那它们可能集中在快速滑动 Listing的时候,加载大批量图片的时候。在我们的应用中,这些大都使用了网上开源的成熟库。

对于 iOS可能泄露的地方,我的建议还是通过已知的代码规范 逆向去寻找。比如 误用 strong类型的delegate; block 中没有使用 weak self等等。

Time Profiler

时间都去哪儿了。这个工具可以找出我们最耗时的操作并定位到代码中。

实际使用过程中,确实发现了一处不合理的耗时循环。

(右侧黑色部分是耗时操作,但不一定是错误的代码)

工具只是统计时间的消耗,更重要的是通过对代码的敏感,分析定位出可优化的代码。举个例子,有这么一段代码。

有个循环在判断时候使用了枚举类型,但由于枚举类型获取不到count,所以直接写死了循环10次。

但其实我们可以在枚举类型的末尾加上TYPE_MAX。这样既拥有了枚举在switch时候的优势,又得到了NSArray获取count的办法。

加载时间

加载时间上,主要看看不同网络下出首页有没有卡死等。公司wifi条件真机上测试大约 1.2s 内加载完毕,而当模拟边缘网络时主线程加载依然迅速,没发现什么问题。

关于启动时间,有两种查看方法。一种是通过上文的时间检测工具,另一种是直接在代码中打log。其中log又有两种打法。

分别说下这两种打法:

CFAbsoluteTime StartTime;
int main(int argc, char **argv) {
StartTime = CFAbsoluteTimeGetCurrent();
// ...
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime);
});
// ...
}

这是通常的打印方法。然而,从main 开始启动有太多的不可控因素。

iOS App启动过程

  • 链接并加载Framework和static lib
  • UIKit初始化
  • 应用程序callback
  • 第一个Core Animation transaction

其中 framework的加载过程我们难以控制,而初始化字体、状态栏、user defaults、main nib等的过程,我们也不需要关心。当然,你可以做的是保持它们尽可能小,没有冗余。

所以,还有一种做法是记录你在 didFinishLaunchingWithOptions 中所耗费的时间。在这里,你可能初始化了许多不必须的第三方库,做了几次网络请求。而这些,我们都可以 lazyLoad,让程序尽快地冷启动。

帧率等

如何优雅地显示帧率标签?

在 QuartzCore.framework里,有一个 CADisplayLink 类。系统在渲染每一帧的时候,都会调用一次CADisplayLink中的 selector, 它有点像一个定时器类,默认为一秒调用60次。

帧率大部分时间都稳定在 60fps,然而有两种情况下会导致它不能以每秒60次的频率调用回调方法。

  1. CPU忙于其他计算,无暇执行屏幕绘制动作。
  2. 执行回调方法所用时间大于重绘每帧的间隔时间。

我们可以利用这个类的特性,写出如下代码来实时显示一个 屏幕帧率 的标签。

CADisplayLink displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; - (void)tick:(CADisplayLink *)displayLink {
//...
self.numberOfFrames++;
NSTimeInterval delta = displayLink.timestamp - self.lastTimestamp; // Less than one second
if (delta < 1) {
return;
} CGFloat fps = self.numberOfFrames / delta; //Update label
....
}

这个fps 的数值,便是每秒帧数。实测当下拉刷新,或加载List过快时,都会往下掉得很厉害。而这便是性能瓶颈所在的关键点。

本文系冰洁原创,遵循 署名-非商业性知识共享进行许可. 转载请在文章开头显眼处注明注明作者和出处 【冰洁】http://www.bingjie.me

开发完iOS应用,接下去你该做的事的更多相关文章

  1. 开发完 iOS 应用,接下去你该做的事

    iOS专项总结 一个应用经过多次迭代后告一段落,接下去我们在技术上还可以做些什么呢?答案是提高代码的整体质量.关于这方面,除了我们常喊的 重构,测试也非常重要. 博主近期给我们的 iOS客户端代码来了 ...

  2. iOS 程序从开发完到上 AppStore 那点事儿

    收录待用,修改转载已取得腾讯云授权 一.账号体系 想要进行iOS开发,除了必备一台装有Mac OS X/Xcode的Mac开发机(iMac or MacBook)之外,还必须要有苹果开发者账号,只有拥 ...

  3. web前端开发与iOS终端开发的异同[转]

    * {-webkit-tap-highlight-color: rgba(0,0,0,0);}html {-webkit-text-size-adjust: none;}body {font-fami ...

  4. Web前端开发与iOS终端开发的异同

    语言 前端和终端作为面向用户端的程序,有个共同特点:需要依赖用户机器的运行环境,所以开发语言基本上是没有选择的,不像后台想用什么就用什么,iOS只能用Objective-C,前端只能javascrip ...

  5. 玩转iOS开发:iOS中的GCD开发(三)

    上一章, 我们了解到了GCD里的一些队列和任务的知识, 也实践了一下, 同时我们也对主队列的一些小情况了解了一下, 比如上一章讲到的卡线程的问题, 如果没有看的朋友可以去看看玩转iOS开发:iOS中的 ...

  6. 【转载国外好文】代工开发一个iOS应用没有那么容易

    导读:这是来自新加坡的 iOS 开发者 Kent Nguyen 发表在1月底的一篇博文.这篇吐槽文在 iOS 开发圈子里流传甚广,从原文150多个评论就可见一斑,现翻译如下. 让我们开门见山吧:做一个 ...

  7. Safari 前端开发调试 iOS 完美解决方案

    转http://www.2cto.com/kf/201403/283404.html afari 前端开发调试 iOS 完美解决方案 2014-03-05      0个评论    来源:Safari ...

  8. 开发一个iOS应用没有那么容易

    导读:这是来自新加坡的 iOS 开发者 Kent Nguyen 发表在1月底的一篇博文.这篇吐槽文在 iOS 开发圈子里流传甚广,从原文150多个评论就可见一斑,现翻译如下. 让我们开门见山吧:做一个 ...

  9. IOS开发笔记 IOS如何访问通讯录

    IOS开发笔记  IOS如何访问通讯录 其实我是反对这类的需求,你说你读我的隐私,我肯定不愿意的. 幸好ios6.0 以后给了个权限控制.当打开app的时候你可以选择拒绝. 实现方法: [plain] ...

随机推荐

  1. [HDU5727]Necklace(二分图最大匹配,枚举)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5727 题意:有N个阴珠子和N个阳珠子,特定序号的阴阳珠子放在一起会让阳珠子暗淡.现在问排放成一个环,如 ...

  2. 业务对象和BAPI

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  3. CUBRID学习笔记 21 查看主键外键索引

    命令 show create table game; game是表名 在web管理中,请在sql标签中查,不要在query中执行. show create table game; === <Re ...

  4. STORM_0005_第一个非常简单的storm topology的提交运行

    配置好storm之后就可以开始在eclipse里面写topology了. 下面是我在网上搜到的一个简单的例子,我按照自己的理解注释了一下. 第一步,创建mvn工程 这是pom.xml文件 <pr ...

  5. Android SDK 更新镜像服务器

    搞个新的电脑,新环境下,SDK总是更新不成功.找了一下,发现有国内的镜像,记录保存一下. Android Tools Android SDK在线更新镜像服务器    中国科学院开源协会镜像站地址:   ...

  6. I2总线

    I2C简介 飞利浦(PHILIPS)公司开发的两线式串行总线 每个器件都有一个唯一的地址识别 1.只要求两条总线线路:一条串行数据线SDA,一条串行时钟线SCL 2.每个连接到总线的器件都可以通过唯一 ...

  7. 操作系统基础知识之————单线程(Thread)与多线程的区别

    单线程(Thread)与多线程的区别 (一)首先了解一下cpu: 随着主频(cpu内核工作时钟频率,表示在CPU内数字脉冲信号震荡的速度,等于外频(系统基本时间)乘倍频)的不断攀升,X86构架的硬件逐 ...

  8. 百度之星Astar2016 Round2A

    All X 等比数列求和一下 A/B MOD C = A MOD (B*C) / B  或者分治一下 Sitting in Line 状压+拓扑dp dp(i, j)表示当前二进制状态为j,当前状态的 ...

  9. tracert命令详解

    一.windows.Linux系统下 tracert ip/网站域名 二.mac traceroute IP/域名 ---------2016-10-10 15:29:07-- source:[1]t ...

  10. Python学习(7)数字

    目录 Python 数字 Python 数字类型转换 Python 数学函数 Python 随机数函数 Python 三角函数 Python 数学常量 Python 数字 Python 数字数据类型用 ...