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次的频率调用回调方法。

  • CPU忙于其他计算,无暇执行屏幕绘制动作。

  • 执行回调方法所用时间大于重绘每帧的间隔时间。

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

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过快时,都会往下掉得很厉害。而这便是性能瓶颈所在的关键点。

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

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

    iOS专项总结 关于 analyze Clang 静态分析器 Slender Faux Pas Warning Leaks Time Profiler 加载时间 iOS App启动过程 帧率等 如何优 ...

  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. Servlet过滤器创建与配置

    例1 创建一个过滤器,实现网站访问计数器的功能,并在web.xml文件的配置中,将网站访问量的初始值设置为5000. (1)创建名称为CountFilter的类,该类实现javax.servlet.F ...

  2. Informatica 常用组件Expression之二 创建EXP组件

    在 Mapping Designer 中选择"转换-创建".选择表达式转换.为它输入一个名称(惯例为 EXP_TransformationName)并单击"确定" ...

  3. STM32串口的设置和库函数的介绍

    串口设置的一般步骤可以总结为如下几个: 1) 串口时钟使能, GPIO时钟使能  2) 串口复位 3)GPIO 端口模式设置 4) 串口参数初始化  5) 开启中断并且初始化 NVIC(如果需要开启中 ...

  4. statickeyword

    static词义:静态的,可以用于修饰变量和方法,static方法块可以优先于构造函数运行. 被static修饰的变量,叫静态变量,静态变量在内存中仅仅有一份拷贝 public static Stri ...

  5. Back Track 5 之 Web踩点 && 网络漏洞

    Web踩点 CMS程序版本探测 Blindelephant 针对WORDPRESS程序的踩点工具,通过比较插件等一系列的指纹,判断版本. 格式: Python Blindelephant.py [参数 ...

  6. Spark性能优化(2)——广播变量、本地缓存目录、RDD操作、数据倾斜

    广播变量 背景 一般Task大小超过10K时(Spark官方建议是20K),需要考虑使用广播变量进行优化.大表小表Join,小表使用广播的方式,减少Join操作. 参考:Spark广播变量与累加器 L ...

  7. CentOS 7 中 hostnamectl 的使用

    hostnamectl 是在 centos7 中新增加的命令,它是用来修改主机名称的,centos7 修改主机名称会比以往容易许多. 用法 # hostnamectl -h -h --help 显示帮 ...

  8. 向量的表示及协方差矩阵 (PCA的理论基础)

    原文:http://blog.csdn.net/songzitea/article/details/18219237 引言 当面对的数据被抽象为一组向量,那么有必要研究一些向量的数学性质.而这些数学性 ...

  9. iOS-数据库sqlite的使用

    .数据库的增删查改的方法 sqlite3_exec(db, [sql UTF8String], NULL, NULL, &erro); 数据库的使用 步骤:01.导入框架<sqlite3 ...

  10. [Algorithm] Write your own Math.pow function in Javascript, using Recursive approach

    /**@abstract * Write your own Math.pow(a int, b int) function * */ function pow (a, b) { let result ...